Skip to content

Commit

Permalink
support base64
Browse files Browse the repository at this point in the history
  • Loading branch information
rcrwhyg committed Aug 27, 2024
1 parent dc8d564 commit 31f7b75
Show file tree
Hide file tree
Showing 13 changed files with 241 additions and 47 deletions.
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
"ABCDEFGHIJKMNPQRSTUVWXYZ",
"genpass",
"rcli",
"subcmd",
"urlsafe",
"zxcvbn"
]
}
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ license = "MIT"

[dependencies]
anyhow = "1.0.86"
base64 = "0.22.1"
clap = { version = "4.5.16", features = ["derive"] }
csv = "1.3.0"
rand = "0.8.5"
Expand Down
1 change: 1 addition & 0 deletions fixtures/b64.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
W3BhY2thZ2VdCm5hbWUgPSAicmNsaSIKdmVyc2lvbiA9ICIwLjEuMCIKYXV0aG9yID0gIlZpbGVuIFdhbmcgPHJjcndoeWdAc2luYS5jb20+IgplZGl0aW9uID0gIjIwMjEiCmxpY2Vuc2UgPSAiTUlUIgoKIyBTZWUgbW9yZSBrZXlzIGFuZCB0aGVpciBkZWZpbml0aW9ucyBhdCBodHRwczovL2RvYy5ydXN0LWxhbmcub3JnL2NhcmdvL3JlZmVyZW5jZS9tYW5pZmVzdC5odG1sCgpbZGVwZW5kZW5jaWVzXQphbnlob3cgPSAiMS4wLjg2IgpiYXNlNjQgPSAiMC4yMi4xIgpjbGFwID0geyB2ZXJzaW9uID0gIjQuNS4xNiIsIGZlYXR1cmVzID0gWyJkZXJpdmUiXSB9CmNzdiA9ICIxLjMuMCIKcmFuZCA9ICIwLjguNSIKc2VyZGUgPSB7IHZlcnNpb24gPSAiMS4wLjIwOSIsIGZlYXR1cmVzID0gWyJkZXJpdmUiXSB9CnNlcmRlX2pzb24gPSAiMS4wLjEyNyIKc2VyZGVfeWFtbCA9ICIwLjkuMzQiCnp4Y3ZibiA9ICIzLjEuMCIK
64 changes: 64 additions & 0 deletions src/cli/base64.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use std::{fmt, str::FromStr};

use clap::Parser;

use super::verify_input_file;

#[derive(Debug, Parser)]
pub enum Base64SubCommand {
Encode(Base64EncodeOpts),
Decode(Base64DecodeOpts),
}

#[derive(Debug, Parser)]
pub struct Base64EncodeOpts {
#[arg(short, long, value_parser = verify_input_file, default_value = "-")]
pub input: String,
#[arg(long, value_parser = parse_base64_format, default_value = "standard")]
pub format: Base64Format,
}

#[derive(Debug, Parser)]
pub struct Base64DecodeOpts {
#[arg(short, long, value_parser = verify_input_file, default_value = "-")]
pub input: String,
#[arg(long, value_parser = parse_base64_format, default_value = "standard")]
pub format: Base64Format,
}

#[derive(Debug, Clone, Copy)]
pub enum Base64Format {
Standard,
UrlSafe,
}

fn parse_base64_format(format: &str) -> Result<Base64Format, anyhow::Error> {
format.parse()
}

impl FromStr for Base64Format {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"standard" => Ok(Base64Format::Standard),
"urlsafe" => Ok(Base64Format::UrlSafe),
v => anyhow::bail!(format!("Unsupported base64 format: {}", v)),
}
}
}

impl From<Base64Format> for &'static str {
fn from(format: Base64Format) -> Self {
match format {
Base64Format::Standard => "standard",
Base64Format::UrlSafe => "urlsafe",
}
}
}

impl fmt::Display for Base64Format {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", Into::<&str>::into(*self))
}
}
44 changes: 2 additions & 42 deletions src/opts.rs → src/cli/csv.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,8 @@
use core::fmt;
use std::{path::Path, str::FromStr};
use std::{fmt, str::FromStr};

use clap::Parser;

#[derive(Debug, Parser)]
#[command(name = "rcli", version, author, about, long_about = None)]
pub struct Opts {
#[command(subcommand)]
pub cmd: Subcommand,
}

#[derive(Debug, Parser)]
pub enum Subcommand {
#[command(name = "csv", about = "Show CSV, or convert CSV to other formats")]
Csv(CsvOpts),
#[command(name = "genpass", about = "Generate a random password")]
GenPass(GenPassOpts),
}
use super::verify_input_file;

#[derive(Debug, Clone, Copy)]
pub enum OutputFormat {
Expand All @@ -42,32 +28,6 @@ pub struct CsvOpts {
pub header: bool,
}

#[derive(Debug, Parser)]
pub struct GenPassOpts {
#[arg(short, long, default_value_t = 16)]
pub length: u8,

#[arg(long, default_value_t = true)]
pub uppercase: bool,

#[arg(long, default_value_t = true)]
pub lowercase: bool,

#[arg(long, default_value_t = true)]
pub number: bool,

#[arg(long, default_value_t = true)]
pub symbol: bool,
}

pub fn verify_input_file(filename: &str) -> Result<String, &'static str> {
if Path::new(filename).exists() {
Ok(filename.into())
} else {
Err("File does not exist")
}
}

fn parse_format(format: &str) -> Result<OutputFormat, anyhow::Error> {
format.parse()
}
Expand Down
19 changes: 19 additions & 0 deletions src/cli/genpass.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use clap::{arg, Parser};

#[derive(Debug, Parser)]
pub struct GenPassOpts {
#[arg(short, long, default_value_t = 16)]
pub length: u8,

#[arg(long, default_value_t = true)]
pub uppercase: bool,

#[arg(long, default_value_t = true)]
pub lowercase: bool,

#[arg(long, default_value_t = true)]
pub number: bool,

#[arg(long, default_value_t = true)]
pub symbol: bool,
}
55 changes: 55 additions & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
mod base64;
mod csv;
mod genpass;

use std::path::Path;

use self::{csv::CsvOpts, genpass::GenPassOpts};
use clap::Parser;

pub use self::{
base64::{Base64Format, Base64SubCommand},
csv::OutputFormat,
};

#[derive(Debug, Parser)]
#[command(name = "rcli", version, author, about, long_about = None)]
pub struct Opts {
#[command(subcommand)]
pub cmd: Subcommand,
}

#[derive(Debug, Parser)]
pub enum Subcommand {
#[command(name = "csv", about = "Show CSV, or convert CSV to other formats")]
Csv(CsvOpts),
#[command(name = "genpass", about = "Generate a random password")]
GenPass(GenPassOpts),
#[command(subcommand)]
Base64(Base64SubCommand),
}

pub fn verify_input_file(filename: &str) -> Result<String, &'static str> {
// if input is "-" or file exists
if filename == "-" || Path::new(filename).exists() {
Ok(filename.into())
} else {
Err("File does not exist")
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_verify_input_file() {
assert_eq!(verify_input_file("-"), Ok("-".into()));
assert_eq!(verify_input_file("*"), Err("File does not exist"));
assert_eq!(verify_input_file("Cargo.toml"), Ok("Cargo.toml".into()));
assert_eq!(
verify_input_file("./not_exist.csv"),
Err("File does not exist")
);
}
}
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mod opts;
mod cli;
mod process;

pub use opts::{Opts, Subcommand};
pub use process::{process_csv, process_genpass};
pub use cli::{Base64Format, Base64SubCommand, Opts, Subcommand};
pub use process::*;
13 changes: 12 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use clap::Parser;
use rcli::{process_csv, process_genpass, Opts, Subcommand};
use rcli::{
process_csv, process_decode, process_encode, process_genpass, Base64SubCommand, Opts,
Subcommand,
};

fn main() -> anyhow::Result<()> {
let opts: Opts = Opts::parse();
Expand All @@ -19,6 +22,14 @@ fn main() -> anyhow::Result<()> {
opts.number,
opts.symbol,
)?,
Subcommand::Base64(subcmd) => match subcmd {
Base64SubCommand::Encode(opts) => {
process_encode(&opts.input, opts.format)?;
}
Base64SubCommand::Decode(opts) => {
process_decode(&opts.input, opts.format)?;
}
},
}

Ok(())
Expand Down
72 changes: 72 additions & 0 deletions src/process/b64.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use std::{fs::File, io::Read};

use base64::{
engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD},
Engine as _,
};

use crate::cli::Base64Format;

pub fn process_encode(input: &str, format: Base64Format) -> anyhow::Result<()> {
let mut reader: Box<dyn Read> = get_reader(input)?;

let mut buf = Vec::new();
reader.read_to_end(&mut buf)?;

let encoded = match format {
Base64Format::Standard => STANDARD.encode(&buf),
Base64Format::UrlSafe => URL_SAFE_NO_PAD.encode(&buf),
};

println!("{}", encoded);
Ok(())
}

pub fn process_decode(input: &str, format: Base64Format) -> anyhow::Result<()> {
let mut reader: Box<dyn Read> = get_reader(input)?;

let mut buf = String::new();
reader.read_to_string(&mut buf)?;
// avoid accidental newlines
let buf = buf.trim();

let decoded = match format {
Base64Format::Standard => STANDARD.decode(buf)?,
Base64Format::UrlSafe => URL_SAFE_NO_PAD.decode(buf)?,
};

// TODO: decoded data might not be string (but for this example, we assume it is)
let decoded = String::from_utf8(decoded)?;
println!("{}", decoded);

Ok(())
}

fn get_reader(input: &str) -> anyhow::Result<Box<dyn Read>> {
let reader: Box<dyn Read> = if input == "-" {
Box::new(std::io::stdin())
} else {
Box::new(File::open(input)?)
};

Ok(reader)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_process_encode() {
let input = "Cargo.toml";
assert!(process_encode(input, Base64Format::Standard).is_ok());
// assert!(process_encode(input, Base64Format::UrlSafe).is_ok());
}

// #[test]
// fn test_process_decode() {
// let input = "fixtures/base64.txt";
// assert!(process_decode(input, Base64Format::Standard).is_ok());
// // assert!(process_decode(input, Base64Format::UrlSafe).is_ok());
// }
}
2 changes: 1 addition & 1 deletion src/process/csv_convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fs;

use crate::opts::OutputFormat;
use crate::cli::OutputFormat;

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "PascalCase")]
Expand Down
2 changes: 2 additions & 0 deletions src/process/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
mod b64;
mod csv_convert;
mod gen_pass;

pub use b64::{process_decode, process_encode};
pub use csv_convert::process_csv;
pub use gen_pass::process_genpass;

0 comments on commit 31f7b75

Please sign in to comment.