Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
151 changes: 151 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -267,6 +274,7 @@ pub struct PrinterBuilder<'a, Writer: Write> {
base: Base,
endianness: Endianness,
character_table: CharacterTable,
include_mode: IncludeMode,
color_scheme: ColorScheme,
}

Expand All @@ -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,
}
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -382,6 +396,7 @@ impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
Base::Hexadecimal => 2,
},
endianness: self.endianness,
include_mode: self.include_mode,
}
}
}
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)]
Expand All @@ -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();

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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();

Expand All @@ -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)
}
}
42 changes: 39 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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>,
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
Expand All @@ -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);
Expand Down