use crate::cli::RupassArgs;
use crate::cli::UserInterface;
use crate::core::app_errors::{GenerationError, Result};
use fluent::{FluentArgs, FluentBundle, FluentResource};
const DEFAULT_SPECIAL_CHARS: &str = "~!@#$%^&*_-+=(){}[]:;<>,.?/";
#[doc(alias = "charset")]
#[doc(alias = "character set")]
pub async fn assemble_character_set(
ui: &mut dyn UserInterface,
bundle: &FluentBundle<FluentResource>,
args: &RupassArgs,
) -> Result<(String, Vec<String>)> {
let (base, req) = assemble_flag_based_charset(args);
let interactive = !args.no_prompt;
let (mut charset, mut req_sets) = if interactive {
ask_user_for_additional_sets(ui, bundle, args, base, req).await?
} else {
(base, req)
};
let special_mode = resolve_special_chars_mode(args, interactive);
let special = resolve_special_characters(ui, bundle, special_mode).await?;
if !special.is_empty() {
charset.push_str(&special);
req_sets.push(special);
}
if charset.is_empty() {
return Err(GenerationError::NoCharacterSet);
}
let charset = remove_duplicate_chars(&charset);
let req_sets = req_sets
.into_iter()
.map(|s| remove_duplicate_chars(&s))
.filter(|s| !s.is_empty())
.collect();
Ok((charset, req_sets))
}
enum SpecialCharsMode<'a> {
Skip,
UseDefault,
UseCustom(&'a str),
AskUser,
}
fn resolve_special_chars_mode(args: &RupassArgs, interactive: bool) -> SpecialCharsMode<'_> {
if args.no_symbols {
return SpecialCharsMode::Skip;
}
if args.all {
return SpecialCharsMode::UseDefault;
}
if let Some(custom) = args.symbols_set.as_ref() {
return SpecialCharsMode::UseCustom(custom.as_str());
}
if args.symbols {
return SpecialCharsMode::UseDefault;
}
if interactive {
SpecialCharsMode::AskUser
} else {
SpecialCharsMode::Skip
}
}
async fn resolve_special_characters(
ui: &mut dyn UserInterface,
bundle: &FluentBundle<FluentResource>,
mode: SpecialCharsMode<'_>,
) -> Result<String> {
match mode {
SpecialCharsMode::Skip => Ok(String::new()),
SpecialCharsMode::UseDefault => Ok(DEFAULT_SPECIAL_CHARS.to_owned()),
SpecialCharsMode::UseCustom(custom) => Ok(custom.to_owned()),
SpecialCharsMode::AskUser => prompt_special_characters(ui, bundle).await,
}
}
fn assemble_flag_based_charset(args: &RupassArgs) -> (String, Vec<String>) {
if args.all {
return (
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".to_string(),
vec![
"0123456789".to_string(),
"ABCDEFGHIJKLMNOPQRSTUVWXYZ".to_string(),
"abcdefghijklmnopqrstuvwxyz".to_string(),
],
);
}
let init = (String::new(), Vec::new());
[
(args.numbers, "0123456789"),
(args.uppercase, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
(args.lowercase, "abcdefghijklmnopqrstuvwxyz"),
]
.iter()
.fold(init, |(mut acc, mut req), (flag, set)| {
if *flag {
acc.push_str(set);
req.push((*set).to_owned());
}
(acc, req)
})
}
fn remove_duplicate_chars(input: &str) -> String {
use std::collections::HashSet;
let mut seen = HashSet::new();
input.chars().filter(|&c| seen.insert(c)).collect()
}
async fn ask_user_for_additional_sets(
ui: &mut dyn UserInterface,
bundle: &FluentBundle<FluentResource>,
args: &RupassArgs,
charset: String,
required: Vec<String>,
) -> Result<(String, Vec<String>)> {
let q = [
(
args.no_uppercase,
"question_uppercase",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
),
(
args.no_lowercase,
"question_lowercase",
"abcdefghijklmnopqrstuvwxyz",
),
(args.no_numbers, "question_numbers", "0123456789"),
];
let (mut acc, mut req) = (charset, required);
for (skip, key, chars) in q {
if skip {
continue;
}
if req.iter().any(|r| r == chars) {
continue;
}
let question = crate::core::utils::fallback_translation(bundle, key, "", None);
if crate::core::utils::ask_user_yes_no(ui, bundle, &question).await? {
acc.push_str(chars);
req.push(chars.to_owned());
}
}
Ok((acc, req))
}
async fn prompt_special_characters(
ui: &mut dyn UserInterface,
bundle: &FluentBundle<FluentResource>,
) -> Result<String> {
let mut fargs = FluentArgs::new();
fargs.set("specialChars", DEFAULT_SPECIAL_CHARS);
let def_msg = crate::core::utils::fallback_translation(
bundle,
"default_special_chars_message",
&format!("Default special chars: {DEFAULT_SPECIAL_CHARS}"),
Some(&fargs),
);
ui.print(&def_msg).await?;
let q = crate::core::utils::fallback_translation(
bundle,
"question_special_chars",
"Use special characters?",
None,
);
if crate::core::utils::ask_user_yes_no(ui, bundle, &q).await? {
ask_special_chars(ui, bundle).await
} else {
Ok(String::new())
}
}
async fn ask_special_chars(
ui: &mut dyn UserInterface,
bundle: &FluentBundle<FluentResource>,
) -> Result<String> {
let change_q = crate::core::utils::fallback_translation(
bundle,
"question_change_special_chars",
"Change the default special chars?",
None,
);
if crate::core::utils::ask_user_yes_no(ui, bundle, &change_q).await? {
let enter_msg = crate::core::utils::fallback_translation(
bundle,
"question_enter_special_chars",
"Enter special chars:",
None,
);
let inp = ui.prompt(&enter_msg).await?;
if inp.is_empty() {
let fallback_msg = crate::core::utils::fallback_translation(
bundle,
"warning_empty_special_chars",
"Empty input received, using default special characters.",
None,
);
ui.print(&fallback_msg).await?;
Ok(DEFAULT_SPECIAL_CHARS.to_owned())
} else {
Ok(inp)
}
} else {
Ok(DEFAULT_SPECIAL_CHARS.to_owned())
}
}