commit 02bd59d84c70cc1d412f68145ba7d1726460091d Author: Nathan Fisher Date: Thu Jan 23 11:57:53 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..80d79c4 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,31 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "b2" +version = "0.1.0" +source = "git+https://git.hitchhiker-linux.org/jeang3nie/b2_rs.git#3ab66346610d681cf764f24bbdd8d5416d7ef1b6" + +[[package]] +name = "base2" +version = "0.1.0" +dependencies = [ + "b2", + "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..54810b5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "base2" +version = "0.1.0" +edition = "2021" + +[dependencies] +b2 = { git = "https://git.hitchhiker-linux.org/jeang3nie/b2_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..6e8766e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,118 @@ +use { + b2::{Encoder, Style}, + 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"); + opts.optopt("s", "style", "output style when encoding, on of plain|spaces|prefixed|table", "STYLE"); + 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 => {} + Operation::Encode => { + let encoder = Encoder::new(reader, writer, None, Some(wrap)); + encoder.encode()?; + } + } + } + + Ok(()) +}