1use crate::*;
2use std::collections::{LinkedList, VecDeque};
3
4
5
6fn read_list<Data>(input_options: &[InputOption<Data>], prompt: Option<String>, default: Option<usize>) -> BoxResult<usize> {
7 if input_options.is_empty() {return Err(Box::new(ListConstraintError::EmptyList));}
8
9 let prompt = prompt.unwrap_or(String::from("Enter one of the following:"));
11 let display_strings =
12 input_options.iter().enumerate()
13 .map(|(i, option)| {
14 option.get_display_string(default.map(|default| i == default))
15 })
16 .collect::<Vec<_>>();
17
18 let (mut all_choose_strings, mut choose_name_mappings, mut choose_name_hidden_flags) = (vec!(), vec!(), vec!());
20 for (i, option) in input_options.iter().enumerate() {
21 if let Some(bulletin_string) = option.bulletin_string.as_deref() {
22 all_choose_strings.push(bulletin_string);
23 choose_name_mappings.push(i);
24 choose_name_hidden_flags.push(false);
25 }
26 all_choose_strings.push(&*option.main_name);
27 choose_name_mappings.push(i);
28 choose_name_hidden_flags.push(false);
29 for alt_name in &option.alt_names {
30 all_choose_strings.push(alt_name);
31 choose_name_mappings.push(i);
32 choose_name_hidden_flags.push(true);
33 }
34 }
35
36 let print_prompt = || {
38 println!("{prompt}");
39 for option in display_strings.iter() {
40 println!("{option}");
41 }
42 println!();
43 };
44
45 if input_options.len() == 1 {
46 print_prompt();
47 println!();
48 println!("Automatically choosing the first option because it is the only option");
49 return Ok(0);
50 }
51
52 print_prompt();
53 let mut input = read_stdin()?;
54
55 loop {
57 if input.is_empty() && let Some(default) = default {
58 return Ok(default);
59 }
60
61 for (i, option) in all_choose_strings.iter().enumerate() {
63 if option.eq_ignore_ascii_case(&input) {
64 let chosen_index = choose_name_mappings[i];
65 return Ok(chosen_index);
66 }
67 }
68
69 println!();
70 println!("Invalid option.");
71
72 if let Some(possible_choose_string_index) = custom_fuzzy_search(&input, &all_choose_strings) {
74 let possible_option_index = choose_name_mappings[possible_choose_string_index];
75 let possible_option = &input_options[possible_option_index];
76 if choose_name_hidden_flags[possible_choose_string_index] {
77 print!("Did you mean to type \"{}\", for option \"{}\"? (enter nothing to confirm, or re-enter input) ", all_choose_strings[possible_choose_string_index], possible_option.main_name);
78 } else {
79 print!("Did you mean \"{}\"? (enter nothing to confirm, or re-enter input) ", all_choose_strings[possible_choose_string_index]);
80 }
81 let new_input = read_stdin()?;
82 if new_input.is_empty() {
83 let chosen_index = possible_option_index;
84 return Ok(chosen_index);
85 }
86 input = new_input;
87 } else {
88 print!("Invalid option, please re-enter input: ");
89 input = read_stdin()?;
90 }
91
92 }
93}
94
95
96
97impl<'a, Data> TryRead for &'a [InputOption<Data>] {
98 type Output = (usize, &'a InputOption<Data>);
99 type Default = usize;
100 fn try_read_line(self, prompt: Option<String>, default: Option<Self::Default>) -> BoxResult<Self::Output> {
101 let chosen_index = read_list(self, prompt, default)?;
102 Ok((chosen_index, &self[chosen_index]))
103 }
104}
105
106impl<Data, const LEN: usize> TryRead for [InputOption<Data>; LEN] {
107 type Output = (usize, InputOption<Data>);
108 type Default = usize;
109 fn try_read_line(self, prompt: Option<String>, default: Option<Self::Default>) -> BoxResult<Self::Output> {
110 let chosen_index = read_list(&self, prompt, default)?;
111 Ok((chosen_index, self.into_iter().nth(chosen_index).expect("chosen index is out of bounds")))
112 }
113}
114
115
116
117#[derive(Debug)]
119pub enum ListConstraintError {
120 EmptyList,
122}
123
124impl Error for ListConstraintError {}
125
126impl Display for ListConstraintError {
127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 match self {
129 Self::EmptyList => write!(f, "List Constraint is empty"),
130 }
131 }
132}
133
134
135
136pub fn custom_fuzzy_search(pattern: &str, items: &[&str]) -> Option<usize> {
138 let (mut best_score, mut best_index) = (custom_fuzzy_match(pattern, items[0]), 0);
139 for (i, item) in items.iter().enumerate().skip(1) {
140 let score = custom_fuzzy_match(pattern, item);
141 if score > best_score {
142 best_score = score;
143 best_index = i;
144 }
145 }
146 if best_score > 0.0 {
147 Some(best_index)
148 } else {
149 None
150 }
151}
152
153pub fn custom_fuzzy_match(pattern: &str, item: &str) -> f32 {
155 let mut best_score = 0.0f32;
156 let offset_start = pattern.len() as isize * -1 + 1;
157 let offset_end = item.len() as isize - 1;
158 for offset in offset_start..=offset_end {
159 let item_slice = &item[offset.max(0) as usize .. (offset + pattern.len() as isize).min(item.len() as isize) as usize];
160 let pattern_slice = &pattern[(offset * -1).max(0) as usize .. (item.len() as isize - offset).min(pattern.len() as isize) as usize];
161 let mut slice_score = 0.0f32;
162 for (item_char, pattern_char) in item_slice.chars().zip(pattern_slice.chars()) {
163 if item_char.eq_ignore_ascii_case(&pattern_char) {
164 slice_score += 3.;
165 } else {
166 slice_score -= 1.;
167 }
168 }
169 slice_score *= 1. - offset as f32 / item.len() as f32 * 0.5; best_score = best_score.max(slice_score);
171 }
172 best_score
173}
174
175
176
177
178
179pub struct InputOption<Data> {
203 pub bulletin_string: Option<String>,
205 pub main_name: String,
207 pub alt_names: Vec<String>,
209 pub data: Data,
211}
212
213impl<Data> InputOption<Data> {
214 pub fn new(bulletin: impl Into<String>, display: impl Into<String>, choose: Vec<impl Into<String>>, data: Data) -> Self {
216 Self {
217 bulletin_string: Some(bulletin.into()),
218 main_name: display.into(),
219 alt_names: choose.into_iter().map(|v| v.into()).collect(),
220 data,
221 }
222 }
223 pub fn new_without_bulletin(display: impl Into<String>, choose: Vec<impl Into<String>>, data: Data) -> Self {
225 Self {
226 bulletin_string: None,
227 main_name: display.into(),
228 alt_names: choose.into_iter().map(|v| v.into()).collect(),
229 data,
230 }
231 }
232 pub fn get_display_string(&self, is_default: Option<bool>) -> String {
234 match (self.bulletin_string.as_deref(), is_default) {
235 (Some(bulletin_string), Some(true )) => format!("[{bulletin_string}]: {}", self.main_name),
236 (Some(bulletin_string), Some(false)) => format!(" {bulletin_string}: {}", self.main_name),
237 (None , Some(true )) => format!("[{}]", self.main_name),
238 (None , Some(false)) => format!(" {} ", self.main_name),
239 (Some(bulletin_string), None ) => format!("{bulletin_string}: {}", self.main_name),
240 (None , None ) => format!("{}", self.main_name),
241 }
242 }
243}
244
245
246
247
248
249impl<'a, T: Display> TryRead for &'a [T] {
250 type Output = (usize, &'a T);
251 type Default = usize;
252 fn try_read_line(self, prompt: Option<String>, default: Option<Self::Default>) -> BoxResult<Self::Output> {
253 let options = self.iter().enumerate()
254 .map(|(i, option)| {
255 InputOption {
256 bulletin_string: Some((i + 1).to_string()),
257 main_name: option.to_string(),
258 alt_names: vec!(),
259 data: (),
260 }
261 })
262 .collect::<Vec<_>>();
263 let chosen_index = (&*options).try_read_line(prompt, default)?.0;
264 Ok((chosen_index, &self[chosen_index]))
265 }
266}
267
268impl<T: Display, const LEN: usize> TryRead for [T; LEN] {
269 type Output = (usize, T);
270 type Default = usize;
271 fn try_read_line(self, prompt: Option<String>, default: Option<Self::Default>) -> BoxResult<Self::Output> {
272 let options = self.iter().enumerate()
273 .map(|(i, option)| {
274 InputOption {
275 bulletin_string: Some((i + 1).to_string()),
276 main_name: option.to_string(),
277 alt_names: vec!(),
278 data: (),
279 }
280 })
281 .collect::<Vec<_>>();
282 let chosen_index = (&*options).try_read_line(prompt, default)?.0;
283 Ok((chosen_index, self.into_iter().nth(chosen_index).expect("chosen index is out of bounds")))
284 }
285}
286
287impl<T: Display> TryRead for Vec<T> {
288 type Output = (usize, T);
289 type Default = usize;
290 fn try_read_line(mut self, prompt: Option<String>, default: Option<Self::Default>) -> BoxResult<Self::Output> {
291 let options = self.iter().enumerate()
292 .map(|(i, option)| {
293 InputOption {
294 bulletin_string: Some((i + 1).to_string()),
295 main_name: option.to_string(),
296 alt_names: vec!(),
297 data: (),
298 }
299 })
300 .collect::<Vec<_>>();
301 let chosen_index = (&*options).try_read_line(prompt, default)?.0;
302 Ok((chosen_index, self.swap_remove(chosen_index)))
303 }
304}
305
306impl<T: Display> TryRead for VecDeque<T> {
307 type Output = (usize, T);
308 type Default = usize;
309 fn try_read_line(mut self, prompt: Option<String>, default: Option<Self::Default>) -> BoxResult<Self::Output> {
310 let options = self.iter().enumerate()
311 .map(|(i, option)| {
312 InputOption {
313 bulletin_string: Some((i + 1).to_string()),
314 main_name: option.to_string(),
315 alt_names: vec!(),
316 data: (),
317 }
318 })
319 .collect::<Vec<_>>();
320 let chosen_index = (&*options).try_read_line(prompt, default)?.0;
321 Ok((chosen_index, self.swap_remove_back(chosen_index).expect("chosen index is out of bounds")))
322 }
323}
324
325impl<T: Display> TryRead for LinkedList<T> {
326 type Output = (usize, T);
327 type Default = usize;
328 fn try_read_line(self, prompt: Option<String>, default: Option<Self::Default>) -> BoxResult<Self::Output> {
329 let options = self.iter().enumerate()
330 .map(|(i, option)| {
331 InputOption {
332 bulletin_string: Some((i + 1).to_string()),
333 main_name: option.to_string(),
334 alt_names: vec!(),
335 data: (),
336 }
337 })
338 .collect::<Vec<_>>();
339 let chosen_index = (&*options).try_read_line(prompt, default)?.0;
340 Ok((chosen_index, self.into_iter().nth(chosen_index).expect("chosen index is out of bounds")))
341 }
342}