binarytext 0.1.2

Binary-to-text encoders / decoders
Documentation
//! This is a CLI tool for encoding / decoding strings or files.
//! The result maybe can be written to stdout or a new file.

use binarytext::base16::Base16;
use binarytext::base32::Base32;
use binarytext::base45::Base45;
use binarytext::base64::Base64;
use binarytext::base85::Base85;
use binarytext::binarytext::BinaryText;
use binarytext::error::BinTxtError;
use binarytext::int::IntEncoder;
use binarytext::stream::*;
use std::env;
use std::str::FromStr;

/// Prints help for this program.
fn help() {
    println!("usage: bintxt -options- -string or filename-");
    println!("Options:");
    println!(" -g                  Guess encoding by trying all valid encodings");
    println!("                     Can't be combined with option \"-f\"");
    println!("                     and can only be used with \"-d\"");
    println!(" -f                  Argument is a file instead of a string");
    println!(" -o -filename-       Output result to file");
    println!(" -e                  Encode string with specified algorithm");
    println!(" -d                  Decode string with specified algorithm");
    println!(" -t -encoding-       Name of encoding type");
    println!("                     The following encoders are available:");
    println!("                     Base16, Base32, Base32Hex, Base45, Base64, Base64URL, Base85");
    println!("                     And the integer based encoders:");
    println!("                     Base36, Base56, Base58, Base62");
    println!(" -b -buffer length-  Length of the read buffer for streaming (default: 8000 bytes)");
    println!(" -w -width-          Number of columns for output (default 0 - no linebreaks)");
    println!(" -h                  Print this help");
}

/// Returns a vec containing all implemented encoder types.
fn all_encodings() -> Vec<Box<dyn BinaryText>> {
    vec![
        Box::new(Base16::default()),
        Box::new(Base32::default()),
        Box::new(Base32::base32hex()),
        Box::new(IntEncoder::base36()),
        Box::new(Base45::default()),
        Box::new(IntEncoder::base56()),
        Box::new(IntEncoder::base58()),
        Box::new(IntEncoder::base62()),
        Box::new(Base64::default()),
        Box::new(Base64::base64url()),
        Box::new(Base85::default()),
    ]
}

/// Returns an Option of the binary-to-text encoder found by name.
fn encoding_by_name(name: &str) -> Result<Box<dyn BinaryText>, BinTxtError> {
    let name_lower = name.to_lowercase();
    for enc in all_encodings() {
        if enc.name().to_lowercase() == name_lower {
            return Ok(enc);
        }
    }
    Err(BinTxtError::UnknownEncoding(String::from(name)))
}

/// Returns a Vec of all valid encoders for an input string.
fn valid_encodings(input: &str) -> Result<Vec<Box<dyn BinaryText>>, BinTxtError> {
    let mut ret = vec![];
    for enc in all_encodings() {
        if enc.is_decodable(input) {
            ret.push(enc);
        }
    }
    if ret.is_empty() {
        let msg = "No suitable encoding found".to_string();
        return Err(BinTxtError::UnknownEncoding(msg));
    }
    Ok(ret)
}

fn run() -> Result<(), BinTxtError> {
    let mut args = env::args().skip(1);
    let mut mode = Mode::Encode;
    let mut file = false;
    let mut name_encoding = String::new();
    let mut input = String::new();
    let mut width = 0usize;
    let mut buf_len = 8000usize;
    let mut filename_out = String::new();
    let mut guess = false;
    while let Some(arg) = args.next() {
        match arg.as_str() {
            "-h" => {
                help();
                return Ok(());
            }
            "-d" => {
                mode = Mode::Decode;
            }
            "-e" => {
                mode = Mode::Encode;
            }
            "-f" => {
                file = true;
            }
            "-g" => {
                guess = true;
            }
            "-b" => match args.next() {
                Some(buf_len_str) => match usize::from_str(&buf_len_str) {
                    Ok(bl) => {
                        buf_len = bl;
                    }
                    Err(_) => {
                        return Err(BinTxtError::InvalidArg(arg));
                    }
                },
                None => {
                    return Err(BinTxtError::MissingArg(arg));
                }
            },
            "-w" => match args.next() {
                Some(width_string) => match usize::from_str(&width_string) {
                    Ok(w) => {
                        width = w;
                    }
                    Err(_) => {
                        return Err(BinTxtError::InvalidArg(arg));
                    }
                },
                None => {
                    return Err(BinTxtError::MissingArg(arg));
                }
            },
            "-t" => match args.next() {
                Some(name) => {
                    name_encoding = name;
                }
                None => {
                    return Err(BinTxtError::MissingArg(arg));
                }
            },
            "-o" => match args.next() {
                Some(name) => {
                    filename_out = name;
                }
                None => {
                    return Err(BinTxtError::MissingArg(arg));
                }
            },
            _ => {
                // Only the last argument is the input string or filename for encoding / decoding
                if !input.is_empty() {
                    return Err(BinTxtError::InvalidArg(input));
                }
                input = arg;
            }
        }
    }
    // Guess if no encoding type is provided
    if name_encoding.is_empty() {
        guess = true;
    }
    if guess {
        if file {
            let msg = "Unable to guess encoding of file - Encoding type needs to be provided using switch \"-f\"".to_string();
            return Err(BinTxtError::UnknownEncoding(msg));
        }
        // For encoding a string we need the encoding type
        if mode == Mode::Encode {
            let msg = "No encoding type provided - Use switch \"-t\" for encoding string with a specific type or \"-d\" for decoding".to_string();
            return Err(BinTxtError::UnknownEncoding(msg));
        }
        // Iterate over all possible encoders and print the result to stdout
        for enc in valid_encodings(&input)? {
            let res = enc.decode_from_str(input.as_str());
            if let Ok(s) = res {
                println!("{}:", enc.name());
                println!("\"{}\"", s);
            }
        }
    } else {
        let enc = encoding_by_name(&name_encoding)?;
        let buf_writer = buf_writer_stdout()?;
        if file {
            let buf_reader = buf_reader_file(&input)?;
            if filename_out.is_empty() {
                let mut bintxt_stream =
                    BinTxtStream::new(buf_reader, buf_writer, &enc, mode, buf_len, width);
                bintxt_stream.stream()?;
            } else {
                let buf_writer = buf_writer_file(&filename_out)?;
                let mut bintxt_stream =
                    BinTxtStream::new(buf_reader, buf_writer, &enc, mode, buf_len, width);
                bintxt_stream.stream()?;
            }
        } else {
            let buf_reader = buf_reader_bytes(input.as_bytes())?;
            let mut bintxt_stream =
                BinTxtStream::new(buf_reader, buf_writer, &enc, mode, buf_len, width);
            bintxt_stream.stream()?;
        }
        println!();
    }
    Ok(())
}

fn main() {
    if let Err(err) = run() {
        eprintln!("{err}");
    }
}