Files
hexyl/src/bin/hexyl.rs
T

179 lines
5.1 KiB
Rust

#[macro_use]
extern crate clap;
use atty;
use ctrlc;
use std::fs::File;
use std::io::{self, prelude::*};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use clap::{App, AppSettings, Arg};
use atty::Stream;
use hexyl::{BorderStyle, Printer};
const BUFFER_SIZE: usize = 256;
fn run() -> Result<(), Box<dyn std::error::Error>> {
let app = App::new(crate_name!())
.setting(AppSettings::ColorAuto)
.setting(AppSettings::ColoredHelp)
.setting(AppSettings::DeriveDisplayOrder)
.setting(AppSettings::UnifiedHelpMessage)
.version(crate_version!())
.about(crate_description!())
.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"),
)
.arg(
Arg::with_name("bytes")
.short("c")
.long("bytes")
.takes_value(true)
.value_name("N")
.help("An alias for -n/--length"),
)
.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")
.help(
"When to use colors. The auto-mode only displays colors if the output \
goes to an interactive terminal",
),
)
.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"),
);
let matches = app.get_matches_safe()?;
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()),
};
let length_arg = matches.value_of("length").or(matches.value_of("bytes"));
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()
} else {
n.parse::<u64>().ok()
}
}) {
reader = Box::new(reader.take(length));
}
let show_color = match matches.value_of("color") {
Some("never") => false,
Some("auto") => atty::is(Stream::Stdout),
_ => true,
};
let border_style = match matches.value_of("border") {
Some("unicode") => BorderStyle::Unicode,
Some("ascii") => BorderStyle::Ascii,
_ => BorderStyle::None,
};
let squeeze = !matches.is_present("nosqueezing");
// 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");
let stdout = io::stdout();
let mut printer = Printer::new(stdout.lock(), show_color, border_style, squeeze);
let mut buffer = [0; BUFFER_SIZE];
'mainloop: loop {
let size = reader.read(&mut buffer)?;
if size == 0 {
break;
}
if cancelled.load(Ordering::SeqCst) {
eprintln!("hexyl has been cancelled.");
std::process::exit(130); // Set exit code to 128 + SIGINT
}
for b in &buffer[..size] {
let res = printer.print_byte(*b);
if res.is_err() {
// Broken pipe
break 'mainloop;
}
}
}
// Finish last line
printer.print_textline().ok();
if !printer.header_was_printed() {
printer.header();
}
printer.footer();
Ok(())
}
fn main() {
// Enable ANSI support for Windows
#[cfg(windows)]
let _ = ansi_term::enable_ansi_support();
let result = run();
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);
}
std::process::exit(1);
}
}