Skip to content

Commit

Permalink
Feature: Add Base64 encode/decode support && refractor cli mod
Browse files Browse the repository at this point in the history
  • Loading branch information
Firstero committed Apr 21, 2024
1 parent dac2ed6 commit 31da7eb
Show file tree
Hide file tree
Showing 13 changed files with 261 additions and 101 deletions.
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.82"
base64 = "0.22.0"
clap = { version = "4.5.4", features = ["derive"] }
csv = "1.3.0"
rand = "0.8.5"
Expand Down
69 changes: 69 additions & 0 deletions src/cli/base64_opts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use clap::Parser;
use std::{fmt::Display, str::FromStr};

use super::parse_input_file;
#[derive(Debug, Parser)]
pub struct Base64Opts {
#[command(subcommand)]
pub subcmd: Base64SubCommand,
}

#[derive(Debug, Parser)]
pub enum Base64SubCommand {
#[command(name = "encode", about = "base64 encode")]
Encode(Base64EncodeOpts),
#[command(name = "decode", about = "base64 decode")]
Decode(Base64DecodeOpts),
}

#[derive(Debug, Parser)]
pub struct Base64EncodeOpts {
#[arg(short, long, value_parser=parse_input_file, required = true, help = "input file path, or '-' for stdin")]
pub input: String,
#[arg(long, default_value = "standard", value_parser=Base64Format::from_str, help = "base64 format: [standard, urlsafe, nopadding]")]
pub format: Base64Format,
}

#[derive(Debug, Parser)]
pub struct Base64DecodeOpts {
#[arg(short, long, value_parser=parse_input_file, help = "input file path, or '-' for stdin")]
pub input: String,
#[arg(long, value_parser=Base64Format::from_str, default_value = "standard", help = "base64 format: [standard, urlsafe, nopadding]")]
pub format: Base64Format,
}

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

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),
"nopadding" => Ok(Base64Format::NoPadding),
v => Err(anyhow::anyhow!("invalid base64 format: {}", v)),
}
}
}

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

impl Display for Base64Format {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", Into::<&str>::into(*self))
}
}
50 changes: 50 additions & 0 deletions src/cli/csv_opts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use super::parse_input_file;
use clap::Parser;
use std::{fmt::Display, str::FromStr};

#[derive(Debug, Parser)]
pub struct CsvOpts {
#[arg(long, default_value_t = true)]
pub header: bool,
#[arg(short, long, default_value_t = ',')]
pub delimiter: char,
#[arg(short, long, required = true, value_parser=parse_input_file)]
pub input: String,
#[arg(short, long)] // default_value_t = "output.json".into()
pub output: Option<String>,
#[arg(long, default_value = "json", value_parser=OutputFormat::from_str)]
pub format: OutputFormat,
}

#[derive(Debug, Clone, Copy)]
pub enum OutputFormat {
Json,
Yaml,
}

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

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"json" => Ok(OutputFormat::Json),
"yaml" => Ok(OutputFormat::Yaml),
v => Err(anyhow::anyhow!("invalid output format: {}", v)),
}
}
}

impl From<OutputFormat> for &'static str {
fn from(f: OutputFormat) -> Self {
match f {
OutputFormat::Json => "json",
OutputFormat::Yaml => "yaml",
}
}
}

impl Display for OutputFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", Into::<&str>::into(*self))
}
}
14 changes: 14 additions & 0 deletions src/cli/genpass_opts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use clap::Parser;
#[derive(Debug, Parser)]
pub struct GenpassOpts {
#[arg(long, default_value_t = false)]
pub no_upper: bool,
#[arg(long, default_value_t = false)]
pub no_lower: bool,
#[arg(long, default_value_t = false)]
pub no_number: bool,
#[arg(long, default_value_t = false)]
pub no_symbol: bool,
#[arg(short, long, default_value_t = 16)]
pub length: u8,
}
52 changes: 52 additions & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
mod base64_opts;
mod csv_opts;
mod genpass_opts;

use std::fs;

pub use self::base64_opts::{Base64Format, Base64Opts, Base64SubCommand};
pub use self::csv_opts::{CsvOpts, OutputFormat};
pub use self::genpass_opts::GenpassOpts;
use clap::Parser;

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

#[derive(Debug, Parser)]
pub enum SubCommand {
// rcli csv --header xx -delimiter , -input /tmp/1.csv -output output.json
#[command(name = "csv", about = "csv file processor")]
Csv(CsvOpts),
// rcli genpass --upper xx --lower --symbol --number --length
#[command(name = "genpass", about = "generate password")]
Genpass(GenpassOpts),
// rcli base64 --encode/decode --output
#[command(name = "base64", about = "base64 encode/decode")]
Base64(Base64Opts),
}

// 模块级别的函数,共享input file的解析逻辑
fn parse_input_file(path: &str) -> Result<String, String> {
if path == "-" || fs::metadata(path).is_ok() {
Ok(path.to_string())
} else {
Err(format!("file not found: {}", path))
}
}

// 单元测试
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_parse_input_file() {
assert_eq!(parse_input_file("-"), Ok("-".to_string()));
assert_eq!(parse_input_file("*"), Err("file not found: *".to_string()));
assert_eq!(parse_input_file("Cargo.toml"), Ok("Cargo.toml".to_string()));
}
}
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
mod opts;
mod cli;
mod process;
pub use opts::{CsvOpts, Opts, SubCommand};
pub use process::{process_csv, process_genpass};
pub use cli::{Base64SubCommand, Opts, SubCommand};
pub use process::{process_b64decode, process_b64encode, process_csv, process_genpass};
24 changes: 17 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
// usage:
// 使用 rcli -- csv --input input.csv --output output.csv --format json
// 使用 rcli -- genpass 进行密码生成
mod opts;
mod cli;
mod process;

use clap::Parser;
use opts::{Opts, SubCommand};
use process::{process_csv, process_genpass};

use cli::{Base64SubCommand, Opts, SubCommand};
use process::{process_b64decode, process_b64encode, process_csv, process_genpass};

// usage:
// 使用 rcli -- csv --input input.csv --output output.csv --format json
// 使用 rcli -- genpass 进行密码生成
// 使用 rcli -- base64 --encode/--decode --format 进行base64编码/解码
fn main() -> anyhow::Result<()> {
let cli = Opts::parse();
match cli.subcmd {
Expand All @@ -26,6 +27,15 @@ fn main() -> anyhow::Result<()> {
opts.length,
)?;
}
// Todo: implement base64 subcommand
SubCommand::Base64(base64_opts) => match base64_opts.subcmd {
Base64SubCommand::Encode(opts) => {
process_b64encode(&opts.input, opts.format)?;
}
Base64SubCommand::Decode(opts) => {
process_b64decode(&opts.input, opts.format)?;
}
},
}
Ok(())
}
88 changes: 0 additions & 88 deletions src/opts.rs

This file was deleted.

42 changes: 42 additions & 0 deletions src/process/base64_processor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use std::io::Read;

use crate::cli::Base64Format;
use base64::prelude::*;

// input 可以是 '-', 表示从 stdin 读取,或者是文件路径, 输出到 stdout, 返回一个 reader
fn get_reader(input: &str) -> anyhow::Result<Box<dyn Read>> {
match input {
"-" => Ok(Box::new(std::io::stdin())),
path => Ok(Box::new(std::fs::File::open(path)?)),
}
}

// base64 encoder,
pub fn process_encode(input: &str, format: Base64Format) -> anyhow::Result<()> {
let mut reader = get_reader(input)?;
// 读取所有的数据
let mut data = Vec::new();
reader.read_to_end(&mut data)?;
let encoded = match format {
Base64Format::Standard => BASE64_STANDARD.encode(&data),
Base64Format::UrlSafe => BASE64_URL_SAFE.encode(&data),
Base64Format::NoPadding => BASE64_URL_SAFE_NO_PAD.encode(&data),
};
println!("{}", encoded);
Ok(())
}

pub fn process_decode(input: &str, format: Base64Format) -> anyhow::Result<()> {
let mut reader = get_reader(input)?;
// 读取所有的数据
let mut data = String::new();
reader.read_to_string(&mut data)?;
let data = data.trim_end();
let decode = match format {
Base64Format::Standard => BASE64_STANDARD.decode(data)?,
Base64Format::UrlSafe => BASE64_URL_SAFE.decode(data)?,
Base64Format::NoPadding => BASE64_URL_SAFE_NO_PAD.decode(data)?,
};
println!("{}", String::from_utf8(decode)?);
Ok(())
}
Loading

0 comments on commit 31da7eb

Please sign in to comment.