use std::{error::Error, fs::OpenOptions, io, process};
use clap::{Parser, ValueEnum};
use ratatui::crossterm::tty::IsTty;
use heh::app::Application;
use heh::decoder::Encoding;
const ABOUT: &str = "
A HEx Helper to edit bytes by the nibble.
Do --help for more information.";
const LONG_ABOUT: &str = "
The HEx Helper is a terminal tool used for modifying binaries by
the nibble. It aims to replicate some of the look of hexyl while
functionally acting like a terminal UI version of GHex.
Note that the octal and hexadecimal labels are slightly
different in heh; they interpret the stream as if 0s were filled
to the end of the byte (i.e. stream length 9 on FF FF would
produce octal 377 200 and hexadecimal FF 80).
Like GHex, you cannot create files with heh, only modify them.
Terminal UI Commands:
ALT= Increase the stream length by 1
ALT- Decrease the stream length by 1
CNTRLs Save
CNTRLq Quit
CNTRLj Jump to Byte
CNTRLe Switch Endianness
CNTRLd Page Down
CNTRLu Page Up
CNTRLf or / Search
CNTRLn or Enter Next Search Match
CNTRLp Prev Search Match
Left-clicking on a label will copy the contents to the clipboard.
Left-clicking on the ASCII or hex table will focus it.
Zooming in and out will change the size of the components.";
#[derive(Parser)]
#[command(version, about = ABOUT, long_about = LONG_ABOUT)]
struct Cli {
#[arg(
value_enum,
short = 'e',
long = "encoding",
default_value = "ascii",
help = "Encoding used for text editor"
)]
encoding: EncodingOption,
#[arg(
value_parser = parse_hex_or_dec,
long = "offset",
default_value = "0",
help="Read file at offset (indicated by a decimal or hexadecimal number)"
)]
offset: usize,
#[arg(help = "File to open")]
file: String,
}
fn main() -> Result<(), Box<dyn Error>> {
if !io::stdout().is_tty() {
eprintln!("Stdout is not a TTY device.");
process::exit(1);
}
let cli = Cli::parse();
let file = OpenOptions::new().read(true).write(true).open(cli.file)?;
let mut app = Application::new(file, cli.encoding.into(), cli.offset)?;
app.run()?;
Ok(())
}
#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum EncodingOption {
Ascii,
Utf8,
}
impl From<EncodingOption> for Encoding {
fn from(encoding: EncodingOption) -> Self {
match encoding {
EncodingOption::Ascii => Encoding::Ascii,
EncodingOption::Utf8 => Encoding::Utf8,
}
}
}
fn parse_hex_or_dec(arg: &str) -> Result<usize, String> {
if let Some(stripped) = arg.strip_prefix("0x") {
usize::from_str_radix(stripped, 16).map_err(|e| format!("Invalid hexadecimal number: {e}"))
} else {
arg.parse().map_err(|e| format!("Invalid decimal number: {e}"))
}
}