Skip to content

Commit 0f647d9

Browse files
committed
first commit
0 parents  commit 0f647d9

File tree

4 files changed

+390
-0
lines changed

4 files changed

+390
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/target

Cargo.lock

Lines changed: 250 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "genpasswd"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
anstyle = "1.0.7"
8+
clap = { version = "4.5.4", features = ["cargo", "color"] }
9+
rand = "0.8.5"

src/main.rs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
use anstyle::{AnsiColor, Effects};
2+
use clap::{arg, builder::styling::Styles, error::ErrorKind, value_parser, Command};
3+
use rand::Rng;
4+
5+
fn main() {
6+
let styles = {
7+
Styles::styled()
8+
.header(AnsiColor::Green.on_default().effects(Effects::BOLD))
9+
.usage(AnsiColor::Green.on_default().effects(Effects::BOLD))
10+
.literal(AnsiColor::Cyan.on_default().effects(Effects::BOLD))
11+
.placeholder(AnsiColor::Cyan.on_default())
12+
.error(AnsiColor::Red.on_default().effects(Effects::BOLD))
13+
.valid(AnsiColor::Yellow.on_default().effects(Effects::BOLD))
14+
.invalid(AnsiColor::Yellow.on_default().effects(Effects::BOLD))
15+
};
16+
17+
let mut cmd = Command::new("genpasswd")
18+
.styles(styles)
19+
.bin_name("genpasswd")
20+
.arg(
21+
arg!(-n --sequences <NUM> "The number of alphanumeric sequences")
22+
.required(false)
23+
.default_value("3")
24+
.value_parser(value_parser!(usize)),
25+
)
26+
.arg(
27+
arg!(-l --length <NUM> "The length of each sequence")
28+
.required(false)
29+
.default_value("6")
30+
.value_parser(value_parser!(usize)),
31+
)
32+
.arg(
33+
arg!(-d --digits <NUM> "The total number of digits")
34+
.required(false)
35+
.default_value("1")
36+
.value_parser(value_parser!(usize)),
37+
)
38+
.arg(
39+
arg!(-u --uppercase <NUM> "The total number of uppercase letters")
40+
.required(false)
41+
.default_value("1")
42+
.value_parser(value_parser!(usize)),
43+
)
44+
.arg(
45+
arg!(-s --separator <CHAR> "The separator character")
46+
.required(false)
47+
.default_value("-")
48+
.value_parser(value_parser!(String)),
49+
);
50+
51+
let matches = cmd.get_matches_mut();
52+
53+
let count = *matches.get_one::<usize>("sequences").unwrap();
54+
let length = *matches.get_one::<usize>("length").unwrap();
55+
let digits = *matches.get_one::<usize>("digits").unwrap();
56+
let uppercase = *matches.get_one::<usize>("uppercase").unwrap();
57+
let separator = matches.get_one::<String>("separator").unwrap();
58+
59+
match validate_parameters(count, length, digits, uppercase) {
60+
Ok(_) => {}
61+
Err(msg) => {
62+
let err = cmd.error(ErrorKind::ValueValidation, msg);
63+
err.exit();
64+
}
65+
}
66+
67+
let password = generate_password(count, length, digits, uppercase, separator);
68+
println!("{}", password);
69+
}
70+
71+
pub fn validate_parameters(
72+
count: usize,
73+
length: usize,
74+
digits: usize,
75+
uppercase: usize,
76+
) -> Result<(), String> {
77+
let total_chars = count * length;
78+
if digits + uppercase > total_chars {
79+
return Err(String::from(
80+
"The number of uppercase letters and digits exceeds the total number of characters.",
81+
));
82+
}
83+
Ok(())
84+
}
85+
86+
pub fn generate_password(
87+
count: usize,
88+
length: usize,
89+
digits: usize,
90+
uppercase: usize,
91+
separator: &str,
92+
) -> String {
93+
let mut rng = rand::thread_rng();
94+
let total_length = count * length;
95+
96+
// Generate positions for digits and uppercase letters
97+
let digit_positions = generate_unique_positions(digits, total_length, &mut rng);
98+
let uppercase_positions = generate_unique_positions(uppercase, total_length, &mut rng);
99+
100+
// Generate password sequences
101+
let mut password_chars: Vec<char> = Vec::with_capacity(total_length);
102+
103+
for i in 0..total_length {
104+
if digit_positions.contains(&i) {
105+
password_chars.push(rng.gen_range('0'..='9'));
106+
} else if uppercase_positions.contains(&i) {
107+
password_chars.push(rng.gen_range('A'..='Z'));
108+
} else {
109+
password_chars.push(rng.gen_range('a'..='z'));
110+
}
111+
}
112+
113+
// Split password into sequences and join with separator
114+
password_chars
115+
.chunks(length)
116+
.map(|chunk| chunk.iter().collect::<String>())
117+
.collect::<Vec<String>>()
118+
.join(separator)
119+
}
120+
121+
fn generate_unique_positions(count: usize, max: usize, rng: &mut impl Rng) -> Vec<usize> {
122+
let mut positions = Vec::with_capacity(count);
123+
while positions.len() < count {
124+
let pos = rng.gen_range(0..max);
125+
if !positions.contains(&pos) {
126+
positions.push(pos);
127+
}
128+
}
129+
positions
130+
}

0 commit comments

Comments
 (0)