mirror of
https://github.com/sharkdp/hexyl.git
synced 2026-10-28 08:05:52 +00:00
179 lines
5.1 KiB
Rust
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);
|
|
}
|
|
}
|