initial commit
This commit is contained in:
commit
eea64e7ef6
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
Cargo.lock
|
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "duralumin"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "libduralumin"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "duralumin"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rand = "0.8.4"
|
||||
clap = "3.0.0-beta.5"
|
89
src/bin/duralumin.rs
Normal file
89
src/bin/duralumin.rs
Normal file
|
@ -0,0 +1,89 @@
|
|||
use clap::Parser;
|
||||
use libduralumin::{PassPhraseGenerator, Words};
|
||||
|
||||
/// program that generates random passphrases.
|
||||
#[derive(Parser)]
|
||||
#[clap(
|
||||
name = "duralumin",
|
||||
version = "0.1.0",
|
||||
author = "No One <noonebtw@nirgendwo.xyz>"
|
||||
)]
|
||||
struct Opts {
|
||||
#[clap(short = 'l', long)]
|
||||
num_words: Option<i16>,
|
||||
#[clap(long)]
|
||||
capitalize: Option<bool>,
|
||||
#[clap(long)]
|
||||
min_word_length: Option<i16>,
|
||||
#[clap(short, long)]
|
||||
count: Option<i16>,
|
||||
#[clap(long, default_value = "useapassphrase")]
|
||||
backend: String,
|
||||
}
|
||||
|
||||
impl Opts {
|
||||
/// gets the number of words per passphrase
|
||||
pub fn words_per_passphrase(&self) -> i16 {
|
||||
self.num_words.unwrap_or(6)
|
||||
}
|
||||
|
||||
/// should the first letter of each word be capitalized?
|
||||
pub fn capitalize(&self) -> bool {
|
||||
self.capitalize.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// gets the number of passphrases to generate
|
||||
pub fn passphrase_count(&self) -> i16 {
|
||||
self.count.unwrap_or(5)
|
||||
}
|
||||
|
||||
/// which source to use for words
|
||||
pub fn backend(&self) -> Option<Backend> {
|
||||
Backend::from_str(&self.backend)
|
||||
}
|
||||
}
|
||||
|
||||
enum Backend {
|
||||
UseAPassPhrase,
|
||||
EnglishWords,
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
pub fn from_str<S>(s: S) -> Option<Self>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
match s.as_ref() {
|
||||
"useapassphrase" => Some(Backend::UseAPassPhrase),
|
||||
"english" => Some(Backend::EnglishWords),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let opts = Opts::parse();
|
||||
let gen = PassPhraseGenerator::new()
|
||||
.with_capitalized(opts.capitalize())
|
||||
.with_min_word_length(opts.min_word_length)
|
||||
.with_length(opts.words_per_passphrase());
|
||||
|
||||
let words = match opts.backend() {
|
||||
Some(Backend::UseAPassPhrase) => {
|
||||
Words::from_str(include_str!("../../useapassphrase.txt"))
|
||||
}
|
||||
Some(Backend::EnglishWords) => {
|
||||
Words::from_str(include_str!("../../words_alpha.txt"))
|
||||
}
|
||||
None => {
|
||||
panic!("invalid backend.")
|
||||
}
|
||||
};
|
||||
|
||||
for passphrase in gen
|
||||
.generate_n(&words, opts.passphrase_count() as usize)
|
||||
.expect("failed to generate passphrases. this is bad.")
|
||||
{
|
||||
println!("{}", passphrase);
|
||||
}
|
||||
}
|
158
src/lib.rs
Normal file
158
src/lib.rs
Normal file
|
@ -0,0 +1,158 @@
|
|||
use std::{
|
||||
fs::File,
|
||||
io::{BufReader, Read},
|
||||
};
|
||||
|
||||
use rand::{prelude::*, rngs::OsRng};
|
||||
|
||||
pub struct Words {
|
||||
words: Vec<String>,
|
||||
}
|
||||
|
||||
impl Words {
|
||||
pub fn new() -> Self {
|
||||
Self { words: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn from_text_file(file: File) -> std::io::Result<Self> {
|
||||
let mut reader = BufReader::new(file);
|
||||
let mut contents = String::new();
|
||||
reader.read_to_string(&mut contents)?;
|
||||
|
||||
Ok(Self::from_str(&contents))
|
||||
}
|
||||
|
||||
pub fn from_str(words: &str) -> Self {
|
||||
Self {
|
||||
words: words.split_whitespace().map(|s| s.to_owned()).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn random_words_with_min_length(
|
||||
&self,
|
||||
word_count: usize,
|
||||
min_word_length: usize,
|
||||
) -> Vec<&str> {
|
||||
self.words
|
||||
.iter()
|
||||
.filter(|word| word.chars().count() >= min_word_length)
|
||||
.map(|word| word.as_str())
|
||||
.choose_multiple(&mut OsRng, word_count)
|
||||
}
|
||||
|
||||
pub fn random_word(&self) -> &str {
|
||||
let i = OsRng.gen_range(0..self.words.len());
|
||||
|
||||
self.words[i].as_str()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PassPhraseGenerator {
|
||||
capitalized: bool,
|
||||
length: i16,
|
||||
min_word_length: Option<i16>,
|
||||
}
|
||||
|
||||
impl PassPhraseGenerator {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
capitalized: false,
|
||||
length: 6,
|
||||
min_word_length: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_capitalized(mut self, capitalized: bool) -> Self {
|
||||
self.capitalized = capitalized;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_length(mut self, length: i16) -> Self {
|
||||
self.length = length;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_min_word_length(mut self, length: Option<i16>) -> Self {
|
||||
self.min_word_length = length;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn generate_n(
|
||||
&self,
|
||||
words: &Words,
|
||||
count: usize,
|
||||
) -> Option<Vec<String>> {
|
||||
(0..count).map(|_| self.generate(words)).collect()
|
||||
}
|
||||
|
||||
pub fn generate(&self, words: &Words) -> Option<String> {
|
||||
let random_words = words.random_words_with_min_length(
|
||||
self.length as usize,
|
||||
self.min_word_length.unwrap_or(0) as usize,
|
||||
);
|
||||
|
||||
if random_words.len() == self.length as usize {
|
||||
Some(
|
||||
random_words
|
||||
.into_iter()
|
||||
.map(|word| {
|
||||
if self.capitalized {
|
||||
let mut chars = word.chars();
|
||||
|
||||
match chars.next() {
|
||||
None => String::new(),
|
||||
Some(c) => {
|
||||
c.to_uppercase().collect::<String>()
|
||||
+ chars.as_str()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
word.to_string()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(" "),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{PassPhraseGenerator, Words};
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
//let words = Words::fr
|
||||
let words = Words::from_str(include_str!("../words_alpha.txt"));
|
||||
let gen = PassPhraseGenerator::new().with_length(4);
|
||||
|
||||
let passphrase = gen.generate(&words);
|
||||
let mut pass_words = passphrase.split_whitespace();
|
||||
assert!(pass_words.next().unwrap().chars().all(|c| c.is_lowercase()));
|
||||
assert!(pass_words.next().is_some());
|
||||
assert!(pass_words.next().is_some());
|
||||
assert!(pass_words.next().is_some());
|
||||
assert!(pass_words.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_capitalize() {
|
||||
//let words = Words::fr
|
||||
let words = Words::from_str(include_str!("../words_alpha.txt"));
|
||||
let gen = PassPhraseGenerator::new()
|
||||
.with_length(1)
|
||||
.with_capitalized(true);
|
||||
|
||||
let passphrase = gen.generate(&words);
|
||||
let mut pass_words = passphrase.split_whitespace();
|
||||
let mut first_word = pass_words.next().unwrap().chars();
|
||||
assert!(first_word.next().unwrap().is_uppercase());
|
||||
assert!(first_word.all(|c| c.is_lowercase()));
|
||||
}
|
||||
}
|
7776
useapassphrase.txt
Normal file
7776
useapassphrase.txt
Normal file
File diff suppressed because it is too large
Load diff
370103
words_alpha.txt
Normal file
370103
words_alpha.txt
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue