use super::Cmd; use clap::{Arg, ArgAction, Command}; use data_encoding::BASE32; use std::{ fs, io::{self, Read}, process, }; #[derive(Debug)] pub struct Base32 { name: &'static str, path: Option, } pub const BASE_32: Base32 = Base32 { name: "base32", path: Some(crate::Path::UsrBin), }; impl Cmd for Base32 { fn name(&self) -> &str { self.name } fn cli(&self) -> clap::Command { Command::new("base32") .version(env!("CARGO_PKG_VERSION")) .author("The JeanG3nie ") .about("Base32 encode/decode data and print to standard output") .args([ Arg::new("INPUT") .help("The input file to use") .num_args(1..), Arg::new("DECODE") .help("Decode rather than encode") .short('d') .long("decode") .action(ArgAction::SetTrue), Arg::new("IGNORE") .help("Ignore whitespace when decoding") .short('i') .long("ignore-space") .action(ArgAction::SetTrue), Arg::new("WRAP") .help("Wrap encoded lines after n characters") .short('w') .long("wrap") .default_value("76"), Arg::new("VERBOSE") .help("Display a header naming each file") .short('v') .long("verbose") .action(ArgAction::SetTrue), Arg::new("QUIET") .help("Do not display header, even with multiple files") .short('q') .long("quiet") .action(ArgAction::SetTrue), ]) } fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box> { let matches = match matches { Some(m) => m, None => return Err(io::Error::new(io::ErrorKind::Other, "No input").into()), }; let files: Vec<_> = match matches.get_many::("INPUT") { Some(c) => c.map(|x| x.to_owned()).collect(), None => vec![String::from("-")], }; let len = files.len(); for (index, file) in files.into_iter().enumerate() { if { len > 1 || matches.get_flag("VERBOSE") } && !matches.get_flag("QUIET") { match index { 0 => println!("===> {} <===", file), _ => println!("\n===> {} <===", file), }; } else if index > 0 { println!(); } let contents = get_contents(&file); if matches.get_flag("DECODE") { decode_base32(contents, matches.get_flag("IGNORE")); } else { encode_base32( &contents, match matches.get_one::("WRAP") { Some(c) => *c, None => { return Err(io::Error::new(io::ErrorKind::Other, "Invalid wrap").into()) } }, ); } } Ok(()) } fn path(&self) -> Option { self.path } } fn decode_base32(mut contents: String, ignore: bool) { if ignore { contents.retain(|c| !c.is_whitespace()); } else { contents = contents.replace('\n', ""); } let decoded = match BASE32.decode(contents.as_bytes()) { Ok(c) => c, Err(e) => { eprintln!("base32: {}", e); process::exit(1); } }; let output = match String::from_utf8(decoded) { Ok(c) => c, Err(e) => { eprintln!("base32: {}", e); process::exit(1); } }; println!("{}", output.trim_end()); } fn encode_base32(contents: &str, wrap: usize) { let encoded = BASE32 .encode(contents.as_bytes()) .chars() .collect::>() .chunks(wrap) .map(|c| c.iter().collect::()) .collect::>(); for line in &encoded { println!("{}", line); } } fn get_contents(file: &str) -> String { let mut contents = String::new(); if file == "-" { match io::stdin().read_to_string(&mut contents) { Ok(_) => true, Err(e) => { eprintln!("base32: {}", e); process::exit(1); } }; } else { contents = match fs::read_to_string(&file) { Ok(c) => c, Err(e) => { eprintln!("base32: {}", e); process::exit(1); } }; } contents }