Files
hexyl/src/main.rs
T

555 lines
15 KiB
Rust
Raw Normal View History

2018-11-05 21:30:39 +01:00
#[macro_use]
extern crate clap;
2019-04-13 18:25:12 +02:00
use atty;
use ctrlc;
2018-11-05 21:30:39 +01:00
2019-03-26 23:24:55 +01:00
mod squeezer;
2018-11-05 21:30:39 +01:00
use std::fs::File;
2018-11-05 21:38:29 +01:00
use std::io::{self, prelude::*, StdoutLock};
2019-01-14 10:26:22 +01:00
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
2018-11-05 21:30:39 +01:00
use clap::{App, AppSettings, Arg};
2019-01-14 09:27:09 +01:00
use ansi_term::Color;
use ansi_term::Color::Fixed;
2018-11-05 22:40:43 +01:00
use atty::Stream;
2019-04-13 18:25:12 +02:00
use crate::squeezer::{SqueezeAction, Squeezer};
2019-01-11 21:32:55 -08:00
const BUFFER_SIZE: usize = 256;
2018-11-05 21:30:39 +01:00
2019-01-14 09:27:09 +01:00
const COLOR_NULL: Color = Fixed(242); // grey
const COLOR_OFFSET: Color = Fixed(242); // grey
const COLOR_ASCII_PRINTABLE: Color = Color::Cyan;
const COLOR_ASCII_WHITESPACE: Color = Color::Green;
const COLOR_ASCII_OTHER: Color = Color::Purple;
const COLOR_NONASCII: Color = Color::Yellow;
2018-11-05 22:40:43 +01:00
enum ByteCategory {
Null,
AsciiPrintable,
AsciiWhitespace,
AsciiOther,
NonAscii,
}
#[derive(Copy, Clone)]
struct Byte(u8);
impl Byte {
fn category(self) -> ByteCategory {
if self.0 == 0x00 {
ByteCategory::Null
} else if self.0.is_ascii_alphanumeric()
|| self.0.is_ascii_punctuation()
|| self.0.is_ascii_graphic()
{
ByteCategory::AsciiPrintable
} else if self.0.is_ascii_whitespace() {
ByteCategory::AsciiWhitespace
} else if self.0.is_ascii() {
ByteCategory::AsciiOther
} else {
ByteCategory::NonAscii
}
}
2019-01-14 09:27:09 +01:00
fn color(self) -> &'static Color {
2019-04-13 18:25:12 +02:00
use crate::ByteCategory::*;
2018-11-05 22:40:43 +01:00
match self.category() {
Null => &COLOR_NULL,
AsciiPrintable => &COLOR_ASCII_PRINTABLE,
AsciiWhitespace => &COLOR_ASCII_WHITESPACE,
AsciiOther => &COLOR_ASCII_OTHER,
NonAscii => &COLOR_NONASCII,
}
}
fn as_char(self) -> char {
2019-04-13 18:25:12 +02:00
use crate::ByteCategory::*;
2018-11-05 22:40:43 +01:00
match self.category() {
Null => '0',
AsciiPrintable => self.0 as char,
2018-11-05 22:51:25 +01:00
AsciiWhitespace if self.0 == 0x20 => ' ',
AsciiWhitespace => '_',
AsciiOther => '•',
NonAscii => '×',
2018-11-05 22:40:43 +01:00
}
}
}
2019-04-08 21:23:09 +02:00
struct BorderElements {
left_corner: char,
horizontal_line: char,
column_separator: char,
right_corner: char,
}
2019-03-05 21:59:52 +01:00
enum BorderStyle {
Unicode,
Ascii,
None,
}
impl BorderStyle {
2019-04-08 21:23:09 +02:00
fn header_elems(&self) -> Option<BorderElements> {
2019-03-05 21:59:52 +01:00
match self {
2019-04-08 21:23:09 +02:00
BorderStyle::Unicode => Some(BorderElements {
left_corner: '┌',
horizontal_line: '─',
column_separator: '┬',
right_corner: '┐',
}),
BorderStyle::Ascii => Some(BorderElements {
left_corner: '+',
horizontal_line: '-',
column_separator: '+',
right_corner: '+',
}),
BorderStyle::None => None,
2019-03-05 21:59:52 +01:00
}
}
2019-04-08 21:23:09 +02:00
fn footer_elems(&self) -> Option<BorderElements> {
2019-03-05 21:59:52 +01:00
match self {
2019-04-08 21:23:09 +02:00
BorderStyle::Unicode => Some(BorderElements {
left_corner: '└',
horizontal_line: '─',
column_separator: '┴',
right_corner: '┘',
}),
BorderStyle::Ascii => Some(BorderElements {
left_corner: '+',
horizontal_line: '-',
column_separator: '+',
right_corner: '+',
}),
BorderStyle::None => None,
2019-03-05 21:59:52 +01:00
}
}
fn outer_sep(&self) -> char {
match self {
BorderStyle::Unicode => '│',
2019-04-08 21:20:45 +02:00
BorderStyle::Ascii => '|',
BorderStyle::None => ' ',
2019-03-05 21:59:52 +01:00
}
}
fn inner_sep(&self) -> char {
match self {
BorderStyle::Unicode => '┊',
2019-04-08 21:20:45 +02:00
BorderStyle::Ascii => '|',
BorderStyle::None => ' ',
2019-03-05 21:59:52 +01:00
}
}
}
2018-11-05 21:38:29 +01:00
struct Printer<'a> {
2018-11-05 21:30:39 +01:00
idx: usize,
/// The raw bytes used as input for the current line.
raw_line: Vec<u8>,
/// The buffered line built with each byte, ready to print to stdout.
buffer_line: Vec<u8>,
2018-11-05 21:38:29 +01:00
stdout: StdoutLock<'a>,
show_color: bool,
2019-03-05 21:59:52 +01:00
border_style: BorderStyle,
header_was_printed: bool,
2019-01-09 21:53:57 -08:00
byte_hex_table: Vec<String>,
byte_char_table: Vec<String>,
2019-03-26 23:24:55 +01:00
squeezer: Squeezer,
2018-11-05 21:30:39 +01:00
}
2018-11-05 21:38:29 +01:00
impl<'a> Printer<'a> {
2019-03-26 23:24:55 +01:00
fn new(
2019-04-13 18:25:12 +02:00
stdout: StdoutLock<'_>,
2019-03-26 23:24:55 +01:00
show_color: bool,
border_style: BorderStyle,
use_squeeze: bool,
2019-04-13 18:25:12 +02:00
) -> Printer<'_> {
2018-11-05 21:38:29 +01:00
Printer {
idx: 1,
raw_line: vec![],
buffer_line: vec![],
2018-11-05 21:38:29 +01:00
stdout,
show_color,
2019-03-05 21:59:52 +01:00
border_style,
header_was_printed: false,
2019-01-09 21:53:57 -08:00
byte_hex_table: (0u8..=u8::max_value())
.map(|i| {
let byte_hex = format!("{:02x} ", i);
if show_color {
Byte(i).color().paint(byte_hex).to_string()
} else {
byte_hex
}
})
2019-01-09 21:53:57 -08:00
.collect(),
byte_char_table: (0u8..=u8::max_value())
.map(|i| {
let byte_char = format!("{}", Byte(i).as_char());
if show_color {
Byte(i).color().paint(byte_char).to_string()
} else {
byte_char
}
2019-01-09 21:53:57 -08:00
})
.collect(),
2019-03-26 23:24:55 +01:00
squeezer: Squeezer::new(use_squeeze),
2018-11-05 21:38:29 +01:00
}
}
2018-11-05 23:04:54 +01:00
fn header(&mut self) {
2019-04-08 21:23:09 +02:00
if let Some(border_elements) = self.border_style.header_elems() {
let h = border_elements.horizontal_line;
2019-03-05 21:59:52 +01:00
let h8 = h.to_string().repeat(8);
let h25 = h.to_string().repeat(25);
writeln!(
self.stdout,
"{l}{h8}{c}{h25}{c}{h25}{c}{h8}{c}{h8}{r}",
2019-04-08 21:23:09 +02:00
l = border_elements.left_corner,
c = border_elements.column_separator,
r = border_elements.right_corner,
2019-04-08 21:20:45 +02:00
h8 = h8,
h25 = h25
)
.ok();
2019-03-05 21:59:52 +01:00
}
2018-11-05 23:04:54 +01:00
}
fn footer(&mut self) {
2019-04-08 21:23:09 +02:00
if let Some(border_elements) = self.border_style.footer_elems() {
let h = border_elements.horizontal_line;
2019-03-05 21:59:52 +01:00
let h8 = h.to_string().repeat(8);
let h25 = h.to_string().repeat(25);
writeln!(
self.stdout,
"{l}{h8}{c}{h25}{c}{h25}{c}{h8}{c}{h8}{r}",
2019-04-08 21:23:09 +02:00
l = border_elements.left_corner,
c = border_elements.column_separator,
r = border_elements.right_corner,
2019-04-08 21:20:45 +02:00
h8 = h8,
h25 = h25
)
.ok();
2019-03-05 21:59:52 +01:00
}
2018-11-05 23:04:54 +01:00
}
2019-03-26 23:24:55 +01:00
fn print_position_indicator(&mut self) {
if !self.header_was_printed {
self.header();
self.header_was_printed = true;
}
let style = COLOR_OFFSET.normal();
let byte_index = format!("{:08x}", self.idx - 1);
let formatted_string = if self.show_color {
format!("{}", style.paint(byte_index))
} else {
byte_index
};
let _ = write!(
&mut self.buffer_line,
"{}{}{} ",
self.border_style.outer_sep(),
formatted_string,
self.border_style.outer_sep()
);
}
2018-11-05 21:38:29 +01:00
fn print_byte(&mut self, b: u8) -> io::Result<()> {
2018-11-05 22:00:11 +01:00
if self.idx % 16 == 1 {
2019-03-26 23:24:55 +01:00
self.print_position_indicator();
2018-11-05 22:00:11 +01:00
}
write!(&mut self.buffer_line, "{}", self.byte_hex_table[b as usize])?;
self.raw_line.push(b);
2018-11-05 21:30:39 +01:00
2019-04-13 18:32:59 +02:00
self.squeezer.process(b, self.idx);
2019-03-26 23:24:55 +01:00
2018-11-05 21:30:39 +01:00
match self.idx % 16 {
8 => {
2019-04-08 21:20:45 +02:00
let _ = write!(&mut self.buffer_line, "{} ", self.border_style.inner_sep());
2018-11-05 21:38:29 +01:00
}
0 => {
self.print_textline()?;
}
2018-11-05 21:30:39 +01:00
_ => {}
}
self.idx += 1;
2018-11-05 21:38:29 +01:00
Ok(())
}
fn print_textline(&mut self) -> io::Result<()> {
let len = self.raw_line.len();
2018-11-05 23:04:54 +01:00
2018-11-05 23:11:03 +01:00
if len == 0 {
2019-03-26 23:24:55 +01:00
if self.squeezer.active() {
self.print_position_indicator();
let _ = writeln!(
&mut self.buffer_line,
"{0:1$}{4}{0:2$}{5}{0:3$}{4}{0:3$}{5}",
"",
24,
25,
8,
self.border_style.inner_sep(),
self.border_style.outer_sep(),
);
self.stdout.write_all(&self.buffer_line)?;
}
2018-11-05 23:11:03 +01:00
return Ok(());
}
2018-11-05 23:53:54 +01:00
if len < 8 {
let _ = write!(
&mut self.buffer_line,
2019-03-05 21:59:52 +01:00
"{0:1$}{3}{0:2$}{4}",
2019-01-09 22:29:47 -08:00
"",
3 * (8 - len),
2019-03-05 21:59:52 +01:00
1 + 3 * 8,
self.border_style.inner_sep(),
self.border_style.outer_sep(),
);
2018-11-05 23:53:54 +01:00
} else {
2019-03-05 21:59:52 +01:00
let _ = write!(
&mut self.buffer_line,
"{0:1$}{2}",
"",
3 * (16 - len),
self.border_style.outer_sep()
);
2018-11-05 23:53:54 +01:00
}
2018-11-05 22:00:11 +01:00
2018-11-05 23:53:54 +01:00
let mut idx = 1;
for &b in self.raw_line.iter() {
let _ = write!(
&mut self.buffer_line,
"{}",
self.byte_char_table[b as usize]
);
2018-11-05 23:04:54 +01:00
if idx == 8 {
2019-04-08 21:20:45 +02:00
let _ = write!(&mut self.buffer_line, "{}", self.border_style.inner_sep());
2018-11-05 23:04:54 +01:00
}
idx += 1;
2018-11-05 22:00:11 +01:00
}
2018-11-05 23:53:54 +01:00
if len < 8 {
2019-03-05 21:59:52 +01:00
let _ = writeln!(
&mut self.buffer_line,
"{0:1$}{3}{0:2$}{4} ",
"",
8 - len,
8,
self.border_style.inner_sep(),
self.border_style.outer_sep(),
);
2018-11-05 23:53:54 +01:00
} else {
2019-03-05 21:59:52 +01:00
let _ = writeln!(
&mut self.buffer_line,
"{0:1$}{2}",
"",
16 - len,
self.border_style.outer_sep()
);
2018-11-05 23:53:54 +01:00
}
2019-03-26 23:24:55 +01:00
match self.squeezer.action() {
SqueezeAction::Print => {
self.buffer_line.clear();
let style = COLOR_OFFSET.normal();
let _ = writeln!(
&mut self.buffer_line,
"{5}{0}{1:2$}{5}{1:3$}{6}{1:3$}{5}{1:4$}{6}{1:4$}{5}",
style.paint("*"),
"",
7,
25,
8,
self.border_style.outer_sep(),
self.border_style.inner_sep(),
);
}
SqueezeAction::Delete => self.buffer_line.clear(),
SqueezeAction::Ignore => (),
}
self.stdout.write_all(&self.buffer_line)?;
2018-11-05 22:00:11 +01:00
self.raw_line.clear();
self.buffer_line.clear();
2018-11-05 21:38:29 +01:00
Ok(())
2018-11-05 21:30:39 +01:00
}
}
2019-04-13 18:25:12 +02:00
fn run() -> Result<(), Box<dyn std::error::Error>> {
2018-11-05 21:30:39 +01:00
let app = App::new(crate_name!())
.setting(AppSettings::ColorAuto)
.setting(AppSettings::ColoredHelp)
.setting(AppSettings::DeriveDisplayOrder)
.setting(AppSettings::UnifiedHelpMessage)
.version(crate_version!())
2018-11-05 23:23:06 +01:00
.about(crate_description!())
2018-11-06 20:18:15 +01:00
.arg(Arg::with_name("file").help("File to display"))
.arg(
Arg::with_name("length")
.short("n")
.long("length")
.takes_value(true)
.value_name("N")
.help("Read only N bytes from the input"),
)
2019-02-04 17:04:26 -06:00
.arg(
Arg::with_name("bytes")
2019-02-04 17:04:26 -06:00
.short("c")
.long("bytes")
2019-02-04 17:04:26 -06:00
.takes_value(true)
.value_name("N")
.help("An alias for -n/--length"),
2019-02-04 17:04:26 -06:00
)
2019-03-26 23:24:55 +01:00
.arg(
Arg::with_name("nosqueezing")
.short("v")
.long("no-squeezing")
.help(
"Displays all input data. Otherwise any number of groups of output \
lines which would be identical to the preceding group of lines, are \
replaced with a line comprised of a single asterisk.",
),
)
.arg(
Arg::with_name("color")
.long("color")
.takes_value(true)
.value_name("when")
.possible_values(&["always", "auto", "never"])
.default_value("always")
2019-01-14 09:27:09 +01:00
.help(
"When to use colors. The auto-mode only displays colors if the output \
goes to an interactive terminal",
),
2019-03-05 21:59:52 +01:00
)
.arg(
Arg::with_name("border")
.long("border")
.takes_value(true)
.possible_values(&["unicode", "ascii", "none"])
.default_value("unicode")
.help("Whether to draw a border with unicode or ASCII characters, or none at all"),
2018-11-06 20:18:15 +01:00
);
2018-11-05 21:30:39 +01:00
2019-01-11 14:06:23 +00:00
let matches = app.get_matches_safe()?;
2018-11-05 21:30:39 +01:00
2018-11-05 23:21:54 +01:00
let stdin = io::stdin();
let mut reader: Box<dyn Read> = match matches.value_of("file") {
Some(filename) => Box::new(File::open(filename)?),
None => Box::new(stdin.lock()),
};
2018-11-05 21:30:39 +01:00
let length_arg = matches.value_of("length").or(matches.value_of("bytes"));
2018-11-05 21:30:39 +01:00
if let Some(length) = length_arg.and_then(|n| {
if n.starts_with("0x") {
u64::from_str_radix(n.trim_start_matches("0x"), 16).ok()
2019-04-08 21:20:45 +02:00
} else {
n.parse::<u64>().ok()
}
2019-04-08 21:20:45 +02:00
}) {
2019-02-04 17:04:26 -06:00
reader = Box::new(reader.take(length));
}
let show_color = match matches.value_of("color") {
Some("never") => false,
Some("auto") => atty::is(Stream::Stdout),
_ => true,
};
2019-03-05 21:59:52 +01:00
let border_style = match matches.value_of("border") {
Some("unicode") => BorderStyle::Unicode,
Some("ascii") => BorderStyle::Ascii,
_ => BorderStyle::None,
};
2019-03-26 23:24:55 +01:00
let squeeze = !matches.is_present("nosqueezing");
2019-01-14 10:26:22 +01:00
// Set up Ctrl-C handler
let cancelled = Arc::new(AtomicBool::new(false));
let c = cancelled.clone();
ctrlc::set_handler(move || {
c.store(true, Ordering::SeqCst);
})
.expect("Error setting Ctrl-C handler");
2018-11-05 21:38:29 +01:00
let stdout = io::stdout();
2019-03-26 23:24:55 +01:00
let mut printer = Printer::new(stdout.lock(), show_color, border_style, squeeze);
2018-11-05 23:04:54 +01:00
2018-11-05 23:21:54 +01:00
let mut buffer = [0; BUFFER_SIZE];
2019-01-14 10:17:14 +01:00
'mainloop: loop {
2018-11-05 23:21:54 +01:00
let size = reader.read(&mut buffer)?;
2018-11-05 21:30:39 +01:00
if size == 0 {
break;
}
2019-01-14 10:26:22 +01:00
if cancelled.load(Ordering::SeqCst) {
eprintln!("hexyl has been cancelled.");
std::process::exit(130); // Set exit code to 128 + SIGINT
}
2018-11-05 21:30:39 +01:00
for b in &buffer[..size] {
2018-11-05 21:38:29 +01:00
let res = printer.print_byte(*b);
if res.is_err() {
// Broken pipe
2019-01-14 10:17:14 +01:00
break 'mainloop;
2018-11-05 21:38:29 +01:00
}
2018-11-05 21:30:39 +01:00
}
}
2018-11-05 21:38:29 +01:00
// Finish last line
printer.print_textline().ok();
2019-04-08 21:34:32 +02:00
if !printer.header_was_printed {
printer.header();
}
2018-11-05 23:04:54 +01:00
printer.footer();
2018-11-05 21:30:39 +01:00
Ok(())
}
fn main() {
2018-11-09 20:04:52 +01:00
// Enable ANSI support for Windows
#[cfg(windows)]
let _ = ansi_term::enable_ansi_support();
2018-11-05 21:30:39 +01:00
let result = run();
2019-01-11 14:07:27 +00:00
if let Err(err) = result {
if let Some(clap_err) = err.downcast_ref::<clap::Error>() {
eprint!("{}", clap_err); // Clap errors already have newlines
match clap_err.kind {
// The exit code should not indicate an error for --help / --version
clap::ErrorKind::HelpDisplayed | clap::ErrorKind::VersionDisplayed => {
std::process::exit(0)
}
_ => (),
}
} else {
eprintln!("Error: {}", err);
}
2019-01-11 14:18:13 +00:00
std::process::exit(1);
2018-11-05 21:30:39 +01:00
}
}