use crate::io::{CliInputOutput, OutputType};
use crate::password::v2::{Password, PasswordStore};
pub const WITH_NUMBERS: bool = true;
pub const WITHOUT_NUMBERS: bool = false;
fn get_list_of_passwords(passwords: &[&Password], with_numbers: bool) -> Vec<String> {
let longest_app_name = passwords.iter().fold(0, |acc, p| {
if p.name.len() > acc {
p.name.len()
} else {
acc
}
});
let longest_username = passwords.iter().fold(0, |acc, p| {
if p.username.len() > acc {
p.username.len()
} else {
acc
}
});
let i_width = ((passwords.len() as f64).log10() + 1_f64).floor() as usize;
let mut list = Vec::new();
for (i, p) in passwords.iter().enumerate() {
let s = match with_numbers {
WITH_NUMBERS => format!(
"{:i_width$} {:app_name_width$} {:username_width$}",
i + 1,
p.name,
p.username,
i_width = i_width,
app_name_width = longest_app_name,
username_width = longest_username,
),
WITHOUT_NUMBERS => format!(
"{:app_name_width$} {:username_width$}",
p.name,
p.username,
app_name_width = longest_app_name,
username_width = longest_username,
),
};
list.push(s);
}
list
}
pub fn print_list_of_passwords(
passwords: &[&Password],
with_numbers: bool,
io: &mut impl CliInputOutput,
) {
let list = get_list_of_passwords(passwords, with_numbers);
for s in list {
io.info(s, OutputType::Standard);
}
}
fn request_password_index_from_stdin(
passwords: &[&Password],
prompt: &str,
io: &mut impl CliInputOutput,
) -> usize {
assert!(!passwords.is_empty());
loop {
if passwords.len() > 1 {
io.info(prompt, OutputType::Standard);
io.write(
format!("Type a number from 1 to {}: ", passwords.len()),
OutputType::Standard,
);
} else if passwords.len() == 1 {
io.write(
"If this is the password you mean, type \"1\" and hit ENTER: ",
OutputType::Standard,
);
}
match io.read_line() {
Ok(line) => {
match line.trim().parse::<usize>() {
Ok(index) => {
if index == 0 || index > passwords.len() {
io.write(
format!(
"I need a number between 1 and {}. Let's try again:",
passwords.len()
),
OutputType::Standard,
);
continue;
}
return index - 1;
}
Err(err) => {
io.write(
format!("This isn't a valid number (reason: {}). Let's try again (1 to {}): ", err, passwords.len()), OutputType::Standard,
);
continue;
}
};
}
Err(err) => {
io.write(
format!(
"I couldn't read that (reason: {}). Let's try again (1 to {}): ",
err,
passwords.len()
),
OutputType::Standard,
);
}
}
}
}
fn choose_password_in_list(
passwords: &[&Password],
with_numbers: bool,
prompt: &str,
io: &mut impl CliInputOutput,
) -> usize {
print_list_of_passwords(passwords, with_numbers, io);
io.nl(OutputType::Standard);
request_password_index_from_stdin(passwords, prompt, io)
}
pub fn search_and_choose_password<'a>(
store: &'a PasswordStore,
query: &str,
with_numbers: bool,
prompt: &str,
io: &mut impl CliInputOutput,
) -> Option<&'a Password> {
let passwords = store.search_passwords(query);
if passwords.is_empty() {
io.error(
format!("Woops, I can't find any passwords for \"{}\".", query),
OutputType::Error,
);
return None;
}
if let Some(&password) = passwords
.iter()
.find(|p| p.name.to_lowercase() == query.to_lowercase())
{
return Some(password);
}
let index = choose_password_in_list(passwords.as_slice(), with_numbers, prompt, io);
Some(passwords[index])
}
#[cfg(test)]
mod test {
use super::get_list_of_passwords;
use crate::list::{WITH_NUMBERS, WITHOUT_NUMBERS};
use crate::password::v2::Password;
use rtoolbox::safe_string::SafeString;
fn get_passwords(mut additional: i32) -> Vec<Password> {
let google = Password::new(
"google".to_string(),
"short un".to_string(),
SafeString::from_string("xxxx".to_string()),
);
let mut list = vec![
Password::new(
"youtube.com".to_string(),
"that long username".to_string(),
SafeString::from_string("xxxx".to_string()),
),
google.clone(),
];
while additional > 0 {
list.push(google.clone());
additional -= 1;
}
list
}
#[test]
fn password_list_has_right_format_with_numbers() {
let passwords = get_passwords(0);
let list = get_list_of_passwords(
passwords.iter().collect::<Vec<&Password>>().as_slice(),
WITH_NUMBERS,
);
assert_eq!(
list,
&[
"1 youtube.com that long username",
"2 google short un ",
]
);
let passwords = get_passwords(8);
let list = get_list_of_passwords(
passwords.iter().collect::<Vec<&Password>>().as_slice(),
WITH_NUMBERS,
);
assert_eq!(
list,
&[
" 1 youtube.com that long username",
" 2 google short un ",
" 3 google short un ",
" 4 google short un ",
" 5 google short un ",
" 6 google short un ",
" 7 google short un ",
" 8 google short un ",
" 9 google short un ",
"10 google short un ",
]
);
}
#[test]
fn password_list_has_right_format_without_numbers() {
let passwords = get_passwords(0);
let list = get_list_of_passwords(
passwords.iter().collect::<Vec<&Password>>().as_slice(),
WITHOUT_NUMBERS,
);
assert_eq!(
list,
&[
"youtube.com that long username",
"google short un ",
]
);
}
}