use crate::*;
use std::collections::{LinkedList, VecDeque};
pub fn read_input_option_enumerated<T: Display + Clone>(choices: &[T], prompt: Option<String>, default: Option<usize>) -> BoxResult<(usize, T)> {
if choices.is_empty() {return Err(Box::new(ListConstraintError::EmptyList));}
let prompt = prompt.unwrap_or(String::from("Enter one of the following:"));
let choice_strings =
choices.iter()
.map(ToString::to_string)
.collect::<Vec<_>>();
let print_prompt = || {
println!("{prompt}");
for (i, choice) in choice_strings.iter().enumerate() {
if let Some(default) = default {
if i == default {
println!("[{choice}]");
} else {
println!(" {choice}");
}
} else {
println!("{choice}");
}
}
println!();
};
if choices.len() == 1 {
print_prompt();
println!();
println!("Automatically choosing {} since it is the only option", choices[0]);
return Ok((0, choices[0].clone()));
}
print_prompt();
let mut input = read_stdin()?;
loop {
if input.is_empty() && let Some(default) = default {
return Ok((default, choices[default].clone()));
}
for (i, choice) in choice_strings.iter().enumerate() {
if choice.eq_ignore_ascii_case(&input) {
return Ok((i, choices[i].clone()));
}
}
println!();
println!("Invalid option.");
let possible_choice_index = custom_fuzzy_search(&input, &choice_strings);
print!("Did you mean \"{}\"? (enter nothing to confirm, or re-enter input) ", choice_strings[possible_choice_index]);
let new_input = read_stdin()?;
if new_input.is_empty() {
return Ok((possible_choice_index, choices[possible_choice_index].clone()));
}
input = new_input;
}
}
pub fn read_input_option<T: Display + Clone>(choices: &[T], prompt: Option<String>, default: Option<usize>) -> BoxResult<T> {
read_input_option_enumerated(choices, prompt, default).map(|(_index, output)| output)
}
#[derive(Debug)]
pub enum ListConstraintError {
EmptyList,
}
impl Error for ListConstraintError {}
impl Display for ListConstraintError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::EmptyList => write!(f, "List Constraint is empty"),
}
}
}
pub fn custom_fuzzy_search(pattern: &str, items: &[String]) -> usize {
let (mut best_score, mut best_index) = (custom_fuzzy_match(pattern, &items[0]), 0);
for (i, item) in items.iter().enumerate().skip(1) {
let score = custom_fuzzy_match(pattern, item);
if score > best_score {
best_score = score;
best_index = i;
}
}
best_index
}
pub fn custom_fuzzy_match(pattern: &str, item: &str) -> usize {
let mut best_score = 0;
let offset_start = pattern.len() as isize * -1 + 1;
let offset_end = item.len() as isize - 1;
for offset in offset_start..=offset_end {
let item_slice = &item[offset.max(0) as usize .. (offset + pattern.len() as isize).min(item.len() as isize) as usize];
let pattern_slice = &pattern[(offset * -1).max(0) as usize .. (item.len() as isize - offset).min(pattern.len() as isize) as usize];
let mut slice_score = 0;
for (item_char, pattern_char) in item_slice.chars().zip(pattern_slice.chars()) {
if item_char.eq_ignore_ascii_case(&pattern_char) {
slice_score += 3;
} else {
slice_score -= 1;
}
}
best_score = (best_score as isize).max(slice_score) as usize;
}
best_score
}
#[derive(Clone, PartialEq)]
pub struct OptionWithData<T: Clone + PartialEq> {
pub display_name: String,
pub data: T,
}
impl<T: Clone + PartialEq> Display for OptionWithData<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.display_name)
}
}
impl<T: Display + Clone + PartialEq> TryRead for &[T] {
type Output = T;
fn try_read_line(&self, prompt: Option<String>, default: Option<Self::Output>) -> BoxResult<Self::Output> {
let default =
self.iter().enumerate()
.find(|v| Some(v.1) == default.as_ref())
.map(|v| v.0);
read_input_option(self, prompt, default)
}
}
impl<T: Display + Clone + PartialEq, const LEN: usize> TryRead for &[T; LEN] {
type Output = T;
fn try_read_line(&self, prompt: Option<String>, default: Option<Self::Output>) -> BoxResult<Self::Output> {
let default =
self.iter().enumerate()
.find(|v| Some(v.1) == default.as_ref())
.map(|v| v.0);
#[allow(clippy::explicit_auto_deref)] read_input_option(*self, prompt, default)
}
}
impl<T: Display + Clone + PartialEq> TryRead for Vec<T> {
type Output = T;
fn try_read_line(&self, prompt: Option<String>, default: Option<Self::Output>) -> BoxResult<Self::Output> {
let default =
self.iter().enumerate()
.find(|v| Some(v.1) == default.as_ref())
.map(|v| v.0);
read_input_option(self, prompt, default)
}
}
impl<T: Display + Clone + PartialEq> TryRead for VecDeque<T> {
type Output = T;
fn try_read_line(&self, prompt: Option<String>, default: Option<Self::Output>) -> BoxResult<Self::Output> {
let default =
self.iter().enumerate()
.find(|v| Some(v.1) == default.as_ref())
.map(|v| v.0);
read_input_option(&self.iter().cloned().collect::<Vec<_>>(), prompt, default)
}
}
impl<T: Display + Clone + PartialEq> TryRead for LinkedList<T> {
type Output = T;
fn try_read_line(&self, prompt: Option<String>, default: Option<Self::Output>) -> BoxResult<Self::Output> {
let default =
self.iter().enumerate()
.find(|v| Some(v.1) == default.as_ref())
.map(|v| v.0);
read_input_option(&self.iter().cloned().collect::<Vec<_>>(), prompt, default)
}
}
pub struct EnumerateInput<T: TryRead> (pub T);
impl<T: Display + Clone + PartialEq> TryRead for EnumerateInput<&[T]> {
type Output = (usize, T);
fn try_read_line(&self, prompt: Option<String>, default: Option<Self::Output>) -> BoxResult<Self::Output> {
let default_index = if let Some((index, _item)) = &default {
Some(*index)
} else {
None
};
read_input_option_enumerated(self.0, prompt, default_index)
}
}
impl<T: Display + Clone + PartialEq, const LEN: usize> TryRead for EnumerateInput<&[T; LEN]> {
type Output = (usize, T);
fn try_read_line(&self, prompt: Option<String>, default: Option<Self::Output>) -> BoxResult<Self::Output> {
let default_index = if let Some((index, _item)) = &default {
Some(*index)
} else {
None
};
read_input_option_enumerated(self.0, prompt, default_index)
}
}
impl<T: Display + Clone + PartialEq> TryRead for EnumerateInput<Vec<T>> {
type Output = (usize, T);
fn try_read_line(&self, prompt: Option<String>, default: Option<Self::Output>) -> BoxResult<Self::Output> {
let default_index = if let Some((index, _item)) = &default {
Some(*index)
} else {
None
};
read_input_option_enumerated(&self.0, prompt, default_index)
}
}
impl<T: Display + Clone + PartialEq> TryRead for EnumerateInput<VecDeque<T>> {
type Output = (usize, T);
fn try_read_line(&self, prompt: Option<String>, default: Option<Self::Output>) -> BoxResult<Self::Output> {
let default_index = if let Some((index, _item)) = &default {
Some(*index)
} else {
None
};
let slice = self.0.iter().cloned().collect::<Vec<_>>();
read_input_option_enumerated(&slice, prompt, default_index)
}
}
impl<T: Display + Clone + PartialEq> TryRead for EnumerateInput<LinkedList<T>> {
type Output = (usize, T);
fn try_read_line(&self, prompt: Option<String>, default: Option<Self::Output>) -> BoxResult<Self::Output> {
let default_index = if let Some((index, _item)) = &default {
Some(*index)
} else {
None
};
let slice = self.0.iter().cloned().collect::<Vec<_>>();
read_input_option_enumerated(&slice, prompt, default_index)
}
}