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
|
|
|
|
|
2019-01-14 09:05:12 +01:00
|
|
|
|
use atty::Stream;
|
2019-04-13 18:25:12 +02:00
|
|
|
|
use crate::squeezer::{SqueezeAction, Squeezer};
|
2019-01-14 09:05:12 +01:00
|
|
|
|
|
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,
|
2019-01-11 20:47:21 -08:00
|
|
|
|
/// 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>,
|
2019-01-11 22:10:20 +10:00
|
|
|
|
show_color: bool,
|
2019-03-05 21:59:52 +01:00
|
|
|
|
border_style: BorderStyle,
|
2019-02-05 18:53:52 -05:00
|
|
|
|
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,
|
2019-01-11 20:47:21 -08:00
|
|
|
|
raw_line: vec![],
|
|
|
|
|
|
buffer_line: vec![],
|
2018-11-05 21:38:29 +01:00
|
|
|
|
stdout,
|
2019-01-11 22:10:20 +10:00
|
|
|
|
show_color,
|
2019-03-05 21:59:52 +01:00
|
|
|
|
border_style,
|
2019-02-05 18:53:52 -05:00
|
|
|
|
header_was_printed: false,
|
2019-01-09 21:53:57 -08:00
|
|
|
|
byte_hex_table: (0u8..=u8::max_value())
|
2019-01-11 22:10:20 +10:00
|
|
|
|
.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| {
|
2019-01-11 22:10:20 +10:00
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
2019-01-11 20:47:21 -08: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 {
|
2019-01-11 20:47:21 -08:00
|
|
|
|
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
|
|
|
|
}
|
2019-02-05 18:53:52 -05: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<()> {
|
2019-01-11 20:47:21 -08:00
|
|
|
|
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 {
|
2019-01-11 20:47:21 -08:00
|
|
|
|
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(),
|
2019-01-11 20:47:21 -08:00
|
|
|
|
);
|
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;
|
2019-01-11 20:47:21 -08:00
|
|
|
|
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 => (),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-01-11 20:47:21 -08:00
|
|
|
|
self.stdout.write_all(&self.buffer_line)?;
|
2018-11-05 22:00:11 +01:00
|
|
|
|
|
2019-01-11 20:47:21 -08: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")
|
2019-01-14 09:05:12 +01:00
|
|
|
|
.help("Read only N bytes from the input"),
|
2019-01-11 22:10:20 +10:00
|
|
|
|
)
|
2019-02-04 17:04:26 -06:00
|
|
|
|
.arg(
|
2019-02-05 06:45:45 -06:00
|
|
|
|
Arg::with_name("bytes")
|
2019-02-04 17:04:26 -06:00
|
|
|
|
.short("c")
|
2019-02-05 06:45:45 -06:00
|
|
|
|
.long("bytes")
|
2019-02-04 17:04:26 -06:00
|
|
|
|
.takes_value(true)
|
2019-02-05 16:37:27 -06:00
|
|
|
|
.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.",
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
2019-01-11 22:10:20 +10:00
|
|
|
|
.arg(
|
2019-01-14 09:05:12 +01:00
|
|
|
|
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
|
|
|
|
|
2019-02-05 06:45:45 -06:00
|
|
|
|
let length_arg = matches.value_of("length").or(matches.value_of("bytes"));
|
2018-11-05 21:30:39 +01:00
|
|
|
|
|
2019-01-19 18:21:19 -07: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 {
|
2019-01-19 18:21:19 -07:00
|
|
|
|
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));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-01-14 09:05:12 +01:00
|
|
|
|
let show_color = match matches.value_of("color") {
|
|
|
|
|
|
Some("never") => false,
|
|
|
|
|
|
Some("auto") => atty::is(Stream::Stdout),
|
|
|
|
|
|
_ => true,
|
|
|
|
|
|
};
|
2019-01-11 22:10:20 +10:00
|
|
|
|
|
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 {
|
2019-01-11 14:17:01 +00:00
|
|
|
|
if let Some(clap_err) = err.downcast_ref::<clap::Error>() {
|
|
|
|
|
|
eprint!("{}", clap_err); // Clap errors already have newlines
|
2019-01-13 17:24:22 +00:00
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
_ => (),
|
|
|
|
|
|
}
|
2019-01-11 14:17:01 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
eprintln!("Error: {}", err);
|
|
|
|
|
|
}
|
2019-01-11 14:18:13 +00:00
|
|
|
|
std::process::exit(1);
|
2018-11-05 21:30:39 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|