Skip to content

Commit

Permalink
Merge pull request #5 from upupnoah/main
Browse files Browse the repository at this point in the history
feat(genpass): support random password generator and zxcvbn
  • Loading branch information
upupnoah authored Jun 11, 2024
2 parents 2e82309 + 4281df0 commit 2ff16be
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 69 deletions.
9 changes: 9 additions & 0 deletions examples/random.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use rand::seq::SliceRandom;
use rand::thread_rng;

fn main() {
let choices = [1, 2, 4, 8, 16, 32];
let mut rng = thread_rng();
println!("{:?}", choices.choose(&mut rng));
assert_eq!(choices[..0].choose(&mut rng), None);
}
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mod opts;
mod process;

pub use opts::{Opts, SubCommand};
pub use process::process_csv;
pub use opts::{Opts, SubCommand, GenPassOpts};
pub use process::{process_csv, process_genpass};
12 changes: 11 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
use clap::Parser;
use rcli::{process_csv, Opts, SubCommand};
use rcli::{process_csv, process_genpass, Opts, SubCommand};

fn main() -> anyhow::Result<()> {
let opts = Opts::parse();
match opts.cmd {
SubCommand::Csv(opts) => {
process_csv(&opts.input, &opts.output, &opts.format)?;
}
SubCommand::GenPass(opts) => {
let password = process_genpass(
opts.length,
opts.upper,
opts.lower,
opts.number,
opts.symbol,
)?;
println!("Generate password: {:?}", password);
}
}
Ok(())
}
24 changes: 24 additions & 0 deletions src/opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ pub struct Opts {
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),
}

#[derive(Debug, Copy, Clone)]
Expand Down Expand Up @@ -50,6 +52,28 @@ pub struct CsvOpts {
pub header: bool,
}

#[derive(Debug, Parser)]
pub struct GenPassOpts {
/// Length of the password
#[arg(short, long, default_value = "16")]
pub length: u8,

/// Uppercase letters
#[arg(long, default_value_t = true)]
pub upper: bool,

/// Lowercase
#[arg(long, default_value_t = true)]
pub lower: bool,

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

/// Symbols
#[arg(long, default_value_t = true)]
pub symbol: bool,
}
// fn verify_input_file(filename: &str) -> Result<String, String> {
// if std::path::Path::new(filename).exists() {
// // Ok(filename.to_string())
Expand Down
70 changes: 4 additions & 66 deletions src/process.rs
Original file line number Diff line number Diff line change
@@ -1,67 +1,5 @@
use std::fs;
mod csv_convert;
mod gen_pass;

use csv::Reader;
use serde::{Deserialize, Serialize};
use serde_json::Value;

use crate::opts::OutputFormat;

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
struct Player {
// #[serde(rename = "Name")]
name: String,
// #[serde(rename = "Position")]
position: String,
#[serde(rename = "DOB")]
dob: String,
// #[serde(rename = "Nationality")]
nationality: String,
#[serde(rename = "Kit Number")]
kit_number: u8,
}

pub fn process_csv(input: &str, output: &str, format: &OutputFormat) -> anyhow::Result<()> {
let mut rdr = Reader::from_path(input)?;
// ***** from for loop to map *****
// let mut records = Vec::new();
// for result in rdr.deserialize() {
// let record: Player = result.unwrap();
// records.push(record);
// }
// let records = rdr
// .deserialize()
// .map(|record| record.unwrap())
// .collect::<Vec<Player>>();

// ***** more universal way with map *****
// let headers = rdr.headers()?.clone();
// let ret = rdr
// .records()
// .map(|record| {
// let record = record.unwrap();
// headers.iter().zip(record.iter()).collect::<Value>() // return Value type
// })
// .collect::<Value>();

// ***** more universal way with for loop *****
let headers = rdr.headers()?.clone();
let mut ret = Vec::with_capacity(128);
for record in rdr.records() {
let record = record?;
// headers.iter() -> use the iterator of headers
// record.iter() -> use the iterator of record
// zip() -> combine the two iterators into one iterator of tuples [(header, record), ...]
// collect::<Value>() -> convert the iterator of tuples into a Value type
let json_value = headers.iter().zip(record.iter()).collect::<Value>();
ret.push(json_value);
}
// let json = serde_json::to_string_pretty(&ret)?;
let content = match format {
OutputFormat::Json => serde_json::to_string_pretty(&ret)?,
OutputFormat::Yaml => serde_yaml::to_string(&ret)?,
// OutputFormat::Toml => toml::to_string(&ret)?,
};
fs::write(output, content)?;
Ok(())
}
pub use csv_convert::process_csv;
pub use gen_pass::process_genpass;
66 changes: 66 additions & 0 deletions src/process/csv_convert.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use std::fs;

use csv::Reader;
use serde_json::Value;

use crate::opts::OutputFormat;

// #[derive(Debug, Serialize, Deserialize)]
// #[serde(rename_all = "PascalCase")]
// struct Player {
// // #[serde(rename = "Name")]
// name: String,
// // #[serde(rename = "Position")]
// position: String,
// #[serde(rename = "DOB")]
// dob: String,
// // #[serde(rename = "Nationality")]
// nationality: String,
// #[serde(rename = "Kit Number")]
// kit_number: u8,
// }

pub fn process_csv(input: &str, output: &str, format: &OutputFormat) -> anyhow::Result<()> {
let mut rdr = Reader::from_path(input)?;
// ***** from for loop to map *****
// let mut records = Vec::new();
// for result in rdr.deserialize() {
// let record: Player = result.unwrap();
// records.push(record);
// }
// let records = rdr
// .deserialize()
// .map(|record| record.unwrap())
// .collect::<Vec<Player>>();

// ***** more universal way with map *****
// let headers = rdr.headers()?.clone();
// let ret = rdr
// .records()
// .map(|record| {
// let record = record.unwrap();
// headers.iter().zip(record.iter()).collect::<Value>() // return Value type
// })
// .collect::<Value>();

// ***** more universal way with for loop *****
let headers = rdr.headers()?.clone();
let mut ret = Vec::with_capacity(128);
for record in rdr.records() {
let record = record?;
// headers.iter() -> use the iterator of headers
// record.iter() -> use the iterator of record
// zip() -> combine the two iterators into one iterator of tuples [(header, record), ...]
// collect::<Value>() -> convert the iterator of tuples into a Value type
let json_value = headers.iter().zip(record.iter()).collect::<Value>();
ret.push(json_value);
}
// let json = serde_json::to_string_pretty(&ret)?;
let content = match format {
OutputFormat::Json => serde_json::to_string_pretty(&ret)?,
OutputFormat::Yaml => serde_yaml::to_string(&ret)?,
// OutputFormat::Toml => toml::to_string(&ret)?,
};
fs::write(output, content)?;
Ok(())
}
88 changes: 88 additions & 0 deletions src/process/gen_pass.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use rand::seq::SliceRandom;
use zxcvbn::zxcvbn;

const UPPER: &[u8] = b"ABCDEFGHJKLMNPQRSTUVWXYZ";
const LOWER: &[u8] = b"abcdefghijkmnopqrstuvwxyz";
const NUMBER: &[u8] = b"123456789";
const SYMBOL: &[u8] = b"!@#$%^&*_";

// pub fn process_genpass(opts: &GenPassOpts) -> anyhow::Result<String> {
// let mut rng = rand::thread_rng();
// let mut password = Vec::new();
// let mut chars = Vec::new();

// if opts.uppercase {
// chars.extend_from_slice(UPPER);
// password.push(*UPPER.choose(&mut rng).expect("UPPER won't be empty"));
// }
// if opts.lowercase {
// chars.extend_from_slice(LOWER);
// password.push(*LOWER.choose(&mut rng).expect("LOWER won't be empty"));
// }
// if opts.number {
// chars.extend_from_slice(NUMBER);
// password.push(*NUMBER.choose(&mut rng).expect("NUMBER won't be empty"));
// }
// if opts.symbol {
// chars.extend_from_slice(SYMBOL);
// password.push(*SYMBOL.choose(&mut rng).expect("SYMBOL won't be empty"));
// }

// for _ in 0..(opts.length - password.len() as u8) {
// let c = chars
// .choose(&mut rng)
// .expect("chars won't be empty in this context");
// password.push(*c);
// }

// password.shuffle(&mut rng);

// Ok(String::from_utf8(password)?)
// }

// 把数据结构拆解出来, 不与 opts.rs 产生耦合
// 好处: 下次想把这部分逻辑拆解分来, 放到其他 repo 的时候, 就会比较方便了
pub fn process_genpass(
length: u8,
upper: bool,
lower: bool,
number: bool,
symbol: bool,
) -> anyhow::Result<String> {
let mut rng = rand::thread_rng();
let mut password = Vec::new();
let mut chars = Vec::new();

if upper {
chars.extend_from_slice(UPPER);
password.push(*UPPER.choose(&mut rng).expect("UPPER won't be empty"));
}
if lower {
chars.extend_from_slice(LOWER);
password.push(*LOWER.choose(&mut rng).expect("LOWER won't be empty"));
}
if number {
chars.extend_from_slice(NUMBER);
password.push(*NUMBER.choose(&mut rng).expect("NUMBER won't be empty"));
}
if symbol {
chars.extend_from_slice(SYMBOL);
password.push(*SYMBOL.choose(&mut rng).expect("SYMBOL won't be empty"));
}

for _ in 0..(length - password.len() as u8) {
let c = chars
.choose(&mut rng)
.expect("chars won't be empty in this context");
password.push(*c);
}

password.shuffle(&mut rng);

let password = String::from_utf8(password)?;

let estimate = zxcvbn(&password, &[]);
println!("Password strength: {}", estimate.score());

Ok(password)
}

0 comments on commit 2ff16be

Please sign in to comment.