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
|
## Features
|
||||||
|
|
||||||
|
- Add option to output result in C include file style, see #242 (@wpcwzy)
|
||||||
- Add `--color-scheme` option, see #247 (@aticu)
|
- Add `--color-scheme` option, see #247 (@aticu)
|
||||||
- Add `braille` character table, see #247 (@aticu)
|
- Add `braille` character table, see #247 (@aticu)
|
||||||
- Add command line argument to generate shell completion, see #155 (@friedz)
|
- Add command line argument to generate shell completion, see #155 (@friedz)
|
||||||
|
|||||||
+151
@@ -24,6 +24,13 @@ pub enum ByteCategory {
|
|||||||
NonAscii,
|
NonAscii,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum IncludeMode {
|
||||||
|
File(String), // filename
|
||||||
|
Stdin,
|
||||||
|
Slice,
|
||||||
|
Off,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, ValueEnum)]
|
#[derive(Copy, Clone, Debug, Default, ValueEnum)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum CharacterTable {
|
pub enum CharacterTable {
|
||||||
@@ -267,6 +274,7 @@ pub struct PrinterBuilder<'a, Writer: Write> {
|
|||||||
base: Base,
|
base: Base,
|
||||||
endianness: Endianness,
|
endianness: Endianness,
|
||||||
character_table: CharacterTable,
|
character_table: CharacterTable,
|
||||||
|
include_mode: IncludeMode,
|
||||||
color_scheme: ColorScheme,
|
color_scheme: ColorScheme,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,6 +292,7 @@ impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
|
|||||||
base: Base::Hexadecimal,
|
base: Base::Hexadecimal,
|
||||||
endianness: Endianness::Big,
|
endianness: Endianness::Big,
|
||||||
character_table: CharacterTable::Default,
|
character_table: CharacterTable::Default,
|
||||||
|
include_mode: IncludeMode::Off,
|
||||||
color_scheme: ColorScheme::Default,
|
color_scheme: ColorScheme::Default,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -338,6 +347,11 @@ impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
|
|||||||
self
|
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 {
|
pub fn color_scheme(mut self, color_scheme: ColorScheme) -> Self {
|
||||||
self.color_scheme = color_scheme;
|
self.color_scheme = color_scheme;
|
||||||
self
|
self
|
||||||
@@ -382,6 +396,7 @@ impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
|
|||||||
Base::Hexadecimal => 2,
|
Base::Hexadecimal => 2,
|
||||||
},
|
},
|
||||||
endianness: self.endianness,
|
endianness: self.endianness,
|
||||||
|
include_mode: self.include_mode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -412,6 +427,8 @@ pub struct Printer<'a, Writer: Write> {
|
|||||||
base_digits: u8,
|
base_digits: u8,
|
||||||
/// Whether to show groups in little or big endian format.
|
/// Whether to show groups in little or big endian format.
|
||||||
endianness: Endianness,
|
endianness: Endianness,
|
||||||
|
/// Whether to output in C include file style.
|
||||||
|
include_mode: IncludeMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Writer: Write> Printer<'a, Writer> {
|
impl<'a, Writer: Write> Printer<'a, Writer> {
|
||||||
@@ -663,6 +680,36 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
|||||||
|
|
||||||
let mut buf = BufReader::new(reader);
|
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 {
|
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) {
|
||||||
@@ -803,6 +850,44 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
|||||||
|
|
||||||
Ok(())
|
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)]
|
#[cfg(test)]
|
||||||
@@ -825,6 +910,7 @@ mod tests {
|
|||||||
.with_base(Base::Hexadecimal)
|
.with_base(Base::Hexadecimal)
|
||||||
.endianness(Endianness::Big)
|
.endianness(Endianness::Big)
|
||||||
.character_table(CharacterTable::Default)
|
.character_table(CharacterTable::Default)
|
||||||
|
.include_mode(IncludeMode::Off)
|
||||||
.color_scheme(ColorScheme::Default)
|
.color_scheme(ColorScheme::Default)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -881,6 +967,7 @@ mod tests {
|
|||||||
.with_base(Base::Hexadecimal)
|
.with_base(Base::Hexadecimal)
|
||||||
.endianness(Endianness::Big)
|
.endianness(Endianness::Big)
|
||||||
.character_table(CharacterTable::Default)
|
.character_table(CharacterTable::Default)
|
||||||
|
.include_mode(IncludeMode::Off)
|
||||||
.color_scheme(ColorScheme::Default)
|
.color_scheme(ColorScheme::Default)
|
||||||
.build();
|
.build();
|
||||||
printer.display_offset(0xdeadbeef);
|
printer.display_offset(0xdeadbeef);
|
||||||
@@ -916,6 +1003,7 @@ mod tests {
|
|||||||
.with_base(Base::Hexadecimal)
|
.with_base(Base::Hexadecimal)
|
||||||
.endianness(Endianness::Big)
|
.endianness(Endianness::Big)
|
||||||
.character_table(CharacterTable::Default)
|
.character_table(CharacterTable::Default)
|
||||||
|
.include_mode(IncludeMode::Off)
|
||||||
.color_scheme(ColorScheme::Default)
|
.color_scheme(ColorScheme::Default)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -977,6 +1065,7 @@ mod tests {
|
|||||||
.with_base(Base::Hexadecimal)
|
.with_base(Base::Hexadecimal)
|
||||||
.endianness(Endianness::Big)
|
.endianness(Endianness::Big)
|
||||||
.character_table(CharacterTable::Default)
|
.character_table(CharacterTable::Default)
|
||||||
|
.include_mode(IncludeMode::Off)
|
||||||
.color_scheme(ColorScheme::Default)
|
.color_scheme(ColorScheme::Default)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -999,4 +1088,66 @@ mod tests {
|
|||||||
.to_owned();
|
.to_owned();
|
||||||
assert_print_all_output(input, expected_string);
|
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 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::{
|
use hexyl::{
|
||||||
COLOR_ASCII_OTHER, COLOR_ASCII_PRINTABLE, COLOR_ASCII_WHITESPACE, COLOR_NONASCII, COLOR_NULL,
|
COLOR_ASCII_OTHER, COLOR_ASCII_PRINTABLE, COLOR_ASCII_WHITESPACE, COLOR_NONASCII, COLOR_NULL,
|
||||||
@@ -194,6 +196,16 @@ struct Opt {
|
|||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
print_color_table: bool,
|
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
|
/// Show shell completion for a certain shell
|
||||||
#[arg(long, value_name("SHELL"))]
|
#[arg(long, value_name("SHELL"))]
|
||||||
completion: Option<Shell>,
|
completion: Option<Shell>,
|
||||||
@@ -262,12 +274,12 @@ fn run() -> Result<()> {
|
|||||||
|
|
||||||
let stdin = io::stdin();
|
let stdin = io::stdin();
|
||||||
|
|
||||||
let mut reader = match opt.file {
|
let mut reader = match &opt.file {
|
||||||
Some(filename) => {
|
Some(filename) => {
|
||||||
if filename.is_dir() {
|
if filename.is_dir() {
|
||||||
bail!("'{}' is a directory.", filename.to_string_lossy());
|
bail!("'{}' is a directory.", filename.to_string_lossy());
|
||||||
}
|
}
|
||||||
let file = File::open(&filename)?;
|
let file = File::open(filename)?;
|
||||||
|
|
||||||
Input::File(file)
|
Input::File(file)
|
||||||
}
|
}
|
||||||
@@ -449,6 +461,29 @@ fn run() -> Result<()> {
|
|||||||
|
|
||||||
let mut stdout = BufWriter::new(io::stdout().lock());
|
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)
|
let mut printer = PrinterBuilder::new(&mut stdout)
|
||||||
.show_color(show_color)
|
.show_color(show_color)
|
||||||
.show_char_panel(show_char_panel)
|
.show_char_panel(show_char_panel)
|
||||||
@@ -460,6 +495,7 @@ fn run() -> Result<()> {
|
|||||||
.with_base(base)
|
.with_base(base)
|
||||||
.endianness(endianness)
|
.endianness(endianness)
|
||||||
.character_table(character_table)
|
.character_table(character_table)
|
||||||
|
.include_mode(include_mode)
|
||||||
.color_scheme(color_scheme)
|
.color_scheme(color_scheme)
|
||||||
.build();
|
.build();
|
||||||
printer.display_offset(skip_offset + display_offset);
|
printer.display_offset(skip_offset + display_offset);
|
||||||
|
|||||||
Reference in new issue
Block a user