diff --git a/crates/lib/data/primitives/src/iterators/iterate_trimmed_lines_without_comments.rs b/crates/lib/data/primitives/src/iterators/iterate_trimmed_lines_without_comments.rs index 1dc928f7e4..0df7e22999 100644 --- a/crates/lib/data/primitives/src/iterators/iterate_trimmed_lines_without_comments.rs +++ b/crates/lib/data/primitives/src/iterators/iterate_trimmed_lines_without_comments.rs @@ -2,20 +2,12 @@ /// Read lines from a file in an iterator. /// Remove comment lines prefixed with `#`. -pub fn iterate_trimmed_lines_without_comments<'a>(iterator: impl Iterator) - -> impl Iterator -//-> impl Iterator +pub fn iterate_trimmed_lines_without_comments<'a>(iterator: impl IntoIterator) + -> impl Iterator { - let f = iterator - .into_iter() - .map(|line| line.trim().to_string()) - .collect::>(); - - let f = f.into_iter() + iterator.into_iter() + .map(|line| line.trim()) .filter(|line| !(line.starts_with("#") || line.is_empty())) - .collect::>(); - - f.into_iter() } #[cfg(test)] @@ -40,13 +32,11 @@ mod tests { let lines = contents.lines(); let lines = iterate_trimmed_lines_without_comments(lines); - let lines: Vec = lines.collect(); - assert_eq!( - lines, + lines.collect::>(), vec![ - "127.0.0.0/24".to_string(), - "192.168.0.0/24".to_string(), + "127.0.0.0/24", + "192.168.0.0/24", ]); } } diff --git a/crates/lib/data/primitives/src/lib.rs b/crates/lib/data/primitives/src/lib.rs index 1dd6495a04..38f1165027 100644 --- a/crates/lib/data/primitives/src/lib.rs +++ b/crates/lib/data/primitives/src/lib.rs @@ -23,6 +23,7 @@ pub mod iterators; pub mod lazy_any_option_true; pub mod numerics; pub mod optional_false_to_none; +pub mod str; pub mod str_to_bool; pub mod traits; pub mod trim_or_empty; diff --git a/crates/lib/data/primitives/src/str/first_letter_uppercase.rs b/crates/lib/data/primitives/src/str/first_letter_uppercase.rs new file mode 100644 index 0000000000..5f566966f8 --- /dev/null +++ b/crates/lib/data/primitives/src/str/first_letter_uppercase.rs @@ -0,0 +1,37 @@ + +/// Converts the first letter of a string to uppercase. +/// eg, "foobar" is converted to "Foobar". +/// From https://stackoverflow.com/a/53570840 +pub fn first_letter_uppercase(s: &str) -> String { + let mut c = s.chars(); + match c.next() { + None => String::new(), + Some(f) => f.to_uppercase().collect::() + c.as_str(), + } +} + +#[cfg(test)] +mod tests { + use crate::str::first_letter_uppercase::first_letter_uppercase; + + #[test] + fn empty() { + assert_eq!(first_letter_uppercase(""), ""); + } + + #[test] + fn lowercase() { + assert_eq!(first_letter_uppercase("f"), "F"); + assert_eq!(first_letter_uppercase("foo"), "Foo"); + assert_eq!(first_letter_uppercase("fOO"), "FOO"); + assert_eq!(first_letter_uppercase("asdf"), "Asdf"); + } + + #[test] + fn uppercase() { + assert_eq!(first_letter_uppercase("F"), "F"); + assert_eq!(first_letter_uppercase("Foo"), "Foo"); + assert_eq!(first_letter_uppercase("FOO"), "FOO"); + assert_eq!(first_letter_uppercase("ASDF"), "ASDF"); + } +} diff --git a/crates/lib/data/primitives/src/str/mod.rs b/crates/lib/data/primitives/src/str/mod.rs new file mode 100644 index 0000000000..f1267184a9 --- /dev/null +++ b/crates/lib/data/primitives/src/str/mod.rs @@ -0,0 +1 @@ +pub mod first_letter_uppercase; \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/util/generate_random_username.rs b/crates/service/web/storyteller_web/src/util/generate_random_username.rs index 8f0301c358..0ca538ed54 100644 --- a/crates/service/web/storyteller_web/src/util/generate_random_username.rs +++ b/crates/service/web/storyteller_web/src/util/generate_random_username.rs @@ -1,35 +1,39 @@ -use filesys::file_lines::iterate_trimmed_file_lines_without_comments::iterate_trimmed_file_lines_without_comments; -use once_cell::sync::Lazy; -use std::collections::HashSet; -use collections::random_from_array::random_from_array; -use primitives::iterators::iterate_trimmed_lines_without_comments::iterate_trimmed_lines_without_comments; use crate::http_server::validations::validate_username::USERNAME_MAX_LENGTH; +use collections::random_from_array::random_from_array; +use once_cell::sync::Lazy; +use primitives::iterators::iterate_trimmed_lines_without_comments::iterate_trimmed_lines_without_comments; +use primitives::str::first_letter_uppercase::first_letter_uppercase; +use rand::distributions::{Distribution, Standard}; +use rand::Rng; +use std::collections::HashSet; pub const ADJECTIVES : &str = include_str!("../../../../../../includes/binary_includes/usernames/atoms/username_adjectives.txt"); pub const NOUNS : &str = include_str!("../../../../../../includes/binary_includes/usernames/atoms/username_nouns.txt"); pub const NOUNS_ANIMALS: &str = include_str!("../../../../../../includes/binary_includes/usernames/atoms/username_nouns_animals.txt"); -fn lines_without_comments() { - -} -static ALL_NOUNS : Lazy> = Lazy::new(|| { +static ALL_NOUNS : Lazy> = Lazy::new(|| { iterate_trimmed_lines_without_comments(NOUNS.lines()) .chain(iterate_trimmed_lines_without_comments(NOUNS_ANIMALS.lines())) - .collect::>() + .collect::>() .into_iter() .collect() }); -static ALL_ADJECTIVES : Lazy> = Lazy::new(|| { +static ALL_ADJECTIVES : Lazy> = Lazy::new(|| { iterate_trimmed_lines_without_comments(ADJECTIVES.lines()) - .collect::>() + .collect::>() .into_iter() - .collect::>() + .collect::>() }); +/// Generate a random username for onboarding purposes. +/// This function is infallible and will always return a possible username. pub fn generate_random_username() -> String { for _ in 0..100 { if let Some(username) = generate_candidate_username() { + if username.len() > USERNAME_MAX_LENGTH { + continue; + } return username; } } @@ -37,20 +41,59 @@ pub fn generate_random_username() -> String { "random_username".to_string() } -fn generate_candidate_username() -> Option { - let maybe_adjective = random_from_array::(&ALL_ADJECTIVES); - let maybe_noun = random_from_array::(&ALL_NOUNS); +enum UsernameFormat { + CamelCase, + KebabCase, + SnakeCase, + CamelKebabCase, + CamelSnakeCase, +} - match (maybe_adjective, maybe_noun) { - (Some(adjective), Some(noun)) => { - let maybe_username = format!("{}{}", adjective, noun); - if maybe_username.len() > USERNAME_MAX_LENGTH { - println!("too long: {}", maybe_username); - return None; - } - Some(maybe_username) - }, - _ => None +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> UsernameFormat { + match rng.gen_range(0..=4) { + 0 => UsernameFormat::CamelCase, + 1 => UsernameFormat::KebabCase, + 2 => UsernameFormat::SnakeCase, + 3 => UsernameFormat::CamelKebabCase, + _ => UsernameFormat::CamelSnakeCase, + } + } +} +fn generate_candidate_username() -> Option { + let adjective = random_from_array(&ALL_ADJECTIVES)?; + let noun = random_from_array(&ALL_NOUNS)?; + + let format : UsernameFormat = rand::random(); + let mut candidate_username = match format { + UsernameFormat::CamelCase => format!("{}{}", first_letter_uppercase(adjective), first_letter_uppercase(noun)), + UsernameFormat::KebabCase => format!("{}-{}", adjective, noun), + UsernameFormat::SnakeCase => format!("{}_{}", adjective, noun), + UsernameFormat::CamelKebabCase => format!("{}-{}", first_letter_uppercase(adjective), first_letter_uppercase(noun)), + UsernameFormat::CamelSnakeCase => format!("{}_{}", first_letter_uppercase(adjective), first_letter_uppercase(noun)), + }; + + if let Some(digit) = random_digit() { + candidate_username = match format { + UsernameFormat::CamelCase => format!("{}{}", candidate_username, digit), + UsernameFormat::KebabCase | UsernameFormat::CamelKebabCase => format!("{}-{}", candidate_username, digit), + UsernameFormat::SnakeCase | UsernameFormat::CamelSnakeCase => format!("{}_{}", candidate_username, digit), + }; + } + + Some(candidate_username) +} + +fn random_digit() -> Option { + // Give uniform probability for the number of digits + let num_digits = rand::thread_rng().gen_range(0..5u8); + match num_digits { + 0 => None, + 1 => Some(rand::thread_rng().gen_range(0..10)), + 2 => Some(rand::thread_rng().gen_range(10..100)), + 3 => Some(rand::thread_rng().gen_range(100..1000)), + 4 => Some(rand::thread_rng().gen_range(1000..10000)), + _ => Some(rand::thread_rng().gen_range(10000..100000)), } } @@ -59,7 +102,7 @@ mod tests { use crate::util::generate_random_username::generate_random_username; #[test] - fn test() { - assert_eq!(generate_random_username(), "asdf".to_string()); + fn test_base_case_success() { + assert!(generate_random_username().len() > 0); } } diff --git a/includes/binary_includes/usernames/atoms/username_adjectives.txt b/includes/binary_includes/usernames/atoms/username_adjectives.txt index e03f679a7b..91de949504 100644 --- a/includes/binary_includes/usernames/atoms/username_adjectives.txt +++ b/includes/binary_includes/usernames/atoms/username_adjectives.txt @@ -1,5 +1,5 @@ --- From https://gist.github.com/hugsy/8910dc78d208e40de42deb29e62df913#file-english-adjectives-txt --- This has been trimmed to remove potentially negative, offensive, uncool, or otherwise bad choices +# From https://gist.github.com/hugsy/8910dc78d208e40de42deb29e62df913#file-english-adjectives-txt +# This has been trimmed to remove potentially negative, offensive, uncool, or otherwise bad choices able absolute diff --git a/includes/binary_includes/usernames/atoms/username_nouns.txt b/includes/binary_includes/usernames/atoms/username_nouns.txt index a22cf65d97..7e7b44cb06 100644 --- a/includes/binary_includes/usernames/atoms/username_nouns.txt +++ b/includes/binary_includes/usernames/atoms/username_nouns.txt @@ -1,6 +1,6 @@ --- From https://gist.github.com/hugsy/8910dc78d208e40de42deb29e62df913 --- https://gist.githubusercontent.com/hugsy/8910dc78d208e40de42deb29e62df913/raw/eec99c5597a73f6a9240cab26965a8609fa0f6ea/english-nouns.txt --- This has been trimmed to remove potentially negative, offensive, uncool, or otherwise bad choices +# From https://gist.github.com/hugsy/8910dc78d208e40de42deb29e62df913 +# https://gist.githubusercontent.com/hugsy/8910dc78d208e40de42deb29e62df913/raw/eec99c5597a73f6a9240cab26965a8609fa0f6ea/english-nouns.txt +# This has been trimmed to remove potentially negative, offensive, uncool, or otherwise bad choices ability account diff --git a/includes/binary_includes/usernames/atoms/username_nouns_animals.txt b/includes/binary_includes/usernames/atoms/username_nouns_animals.txt index e80fa31b8c..c2786b1054 100644 --- a/includes/binary_includes/usernames/atoms/username_nouns_animals.txt +++ b/includes/binary_includes/usernames/atoms/username_nouns_animals.txt @@ -1,6 +1,6 @@ --- From: https://github.com/jneidel/animal-names --- https://github.com/jneidel/animal-names/blob/master/animals-common.txt --- This has been trimmed to remove potentially negative, offensive, uncool, or otherwise bad choices +# From: https://github.com/jneidel/animal-names +# https://github.com/jneidel/animal-names/blob/master/animals-common.txt +# This has been trimmed to remove potentially negative, offensive, uncool, or otherwise bad choices alligator alpaca