commit c387ea0685fd8537da344f8b7e675855a96f2afa Author: Nathan Fisher Date: Sun Jan 12 18:49:06 2025 -0500 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..31b4b8c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,31 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "b32" +version = "0.1.0" +source = "git+https://git.hitchhiker-linux.org/jeang3nie/b32_rs.git#8ceaeff9b70aec6d271ba5f1be45d2c42e2894c3" + +[[package]] +name = "base32" +version = "0.1.0" +dependencies = [ + "b32", + "getopts", +] + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c2eb58d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "base32" +version = "0.1.0" +edition = "2021" + +[dependencies] +b32 = { git = "https://git.hitchhiker-linux.org/jeang3nie/b32_rs.git" } +getopts = "0.2" + +[profile.release] +lto = true +codegen-units = 1 +strip = true + diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..2cefa3a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,121 @@ +use { + b32::{Decoder, Encoder}, + getopts::Options, + std::{ + env, + error::Error, + fs::File, + io::{self, BufReader, BufWriter, Write}, + os::fd::AsFd, + path::Path + } +}; + +#[derive(PartialEq)] +enum Operation { + Encode, + Decode, +} + +enum Input { + Stdin, + Filename(String), +} + +fn print_usage(program: &str, opts: Options) { + let brief = format!("Usage: {} FILE [options]", program); + print!("{}", opts.usage(&brief)); +} + +fn main() -> Result<(), Box> { + let args = env::args().collect::>(); + let progname = Path::new(&args[0]) + .file_name() + .map(|x| x.to_string_lossy()) + .unwrap(); + let mut opts = Options::new(); + opts.optflag("h", "help", "print this help message"); + opts.optflag("d", "decode", "decode instead of encoding"); + opts.optflag("i", "ignore", "ignore whitespace when decoding"); + opts.optopt("o", "output", "output to FILE instead of STDOUT", "FILE"); + opts.optflag("q", "quiet", "never print headers giving file names"); + opts.optflag("v", "verbose", "always print headers giving file names"); + opts.optopt("w", "wrap", "wrap at columns", "COLS"); + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => { + panic!("{}", f.to_string()) + } + }; + if matches.opt_present("h") { + print_usage(&progname, opts); + return Ok(()); + } + let op = if matches.opt_present("d") { + Operation::Decode + } else { + Operation::Encode + }; + let ignore = matches.opt_present("i"); + let wrap: usize = if let Some(w) = matches.opt_str("w") { + w.parse()? + } else { + 76 + }; + + let mut infiles = matches + .free + .iter() + .map(|x| { + if x == "-" { + Input::Stdin + } else { + Input::Filename(x.to_string()) + } + }) + .collect::>(); + if infiles.is_empty() { + infiles.push(Input::Stdin) + } + if matches.opt_present("q") && matches.opt_present("v") { + eprintln!("Error: conflicting options \"verbose\" and \"quiet\" are both present."); + print_usage(&progname, opts); + return Err("Conflicting options".into()); + } + let verbose = matches.opt_present("v") || (matches.free.len() > 1 && !matches.opt_present("q")); + for f in &infiles { + let mut writer = if let Some(o) = matches.opt_str("o") { + if matches.free.len() < 2 || op == Operation::Encode { + BufWriter::new(File::open(o)?) + } else { + panic!("Attempt to decode multiple files into single file"); + } + } else { + BufWriter::new(File::from(io::stdout().as_fd().try_clone_to_owned()?)) + }; + let reader = match f { + Input::Stdin => BufReader::new(File::from(io::stdin().as_fd().try_clone_to_owned()?)), + Input::Filename(f) => BufReader::new(File::open(f)?), + }; + if verbose { + let fname = match f { + Input::Stdin => "Stdin", + Input::Filename(f) => f, + }; + writer.write_fmt(format_args!("==> {fname} <==\n"))?; + } + match op { + Operation::Decode => { + let decoder = Decoder::new(reader, writer, None, ignore); + decoder.decode()?; + } + Operation::Encode => { + let mut encoder = Encoder::new(reader, writer, None, Some(wrap)); + encoder.encode()?; + encoder.output().write_all(&[b'\n'])?; + } + } + } + + Ok(()) +}