initial commit

This commit is contained in:
NoOneBtw 2021-11-17 02:54:40 +01:00
commit eea64e7ef6
6 changed files with 378145 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
Cargo.lock

17
Cargo.toml Normal file
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

370103
words_alpha.txt Normal file

File diff suppressed because it is too large Load diff