1pub struct Ask<T>{
2 questions: Vec<T>
3}
4
5impl <T>Ask<T>
6 where T : question::TraitQuestion {
7 pub fn new()->Ask<T> {
8 Ask{
9 questions: vec!()
10 }
11 }
12
13 pub fn question(&mut self, question_obj:T){
14 self.questions.push(question_obj);
15 }
16
17 pub fn all(&mut self){
18 for question in &mut self.questions {
19 &question.send();
20 }
21 }
22}
23
24pub mod validate{
25 pub mod group_options{
26 use crate::question::AnswerCollection;
27
28 pub fn selections_are_gtoe(answer_collection:& AnswerCollection, num : u16)->bool{
29 let mut total_selected_answers = 0;
30 for answer_set in & answer_collection.answer_sets{
31 total_selected_answers += answer_set.answers.len() as u16;
32 }
33 total_selected_answers >= num
34 }
35
36 pub fn selections_are_ltoe(answer_collection:& AnswerCollection, num : u16)->bool{
37 let mut total_selected_answers = 0;
38 for answer_set in & answer_collection.answer_sets{
39 total_selected_answers += answer_set.answers.len() as u16;
40 }
41 total_selected_answers <= num
42 }
43 }
44}
45
46pub mod question{
47
48 use std::io::{Write, stdout, Stdout, stdin};
49 use termion::{input::TermRead, event::Key, cursor::DetectCursorPos};
50 use termion::raw::IntoRawMode;
51 use regex::Regex;
52
53
54 pub struct QuestionOption{
61 name : String,
62 selected : bool,
63 }
64
65 impl QuestionOption{
66 pub fn new(name :String)->QuestionOption{
67 QuestionOption{
68 name : name,
69 selected : false,
70 }
71 }
72
73 pub fn toggle_selected(mut self, switch : Option<bool>)->Self{
74 self.selected = if let Some(tf) = switch { tf }else{ !self.selected };
75 self
76 }
77 }
78
79 pub struct OptionGroup{
80 name : &'static str,
81 options : Vec<QuestionOption>,
82 options_len : u16,
83 }
84
85 impl OptionGroup {
86 pub fn new(group_name : &'static str) -> OptionGroup{
87 OptionGroup{
88 name : group_name,
89 options : vec!(),
90 options_len : 0,
91 }
92 }
93
94 fn to_answerset(&self)-> AnswerSet{
103 AnswerSet{
104 group_name : self.name,
105 answers : self.get_all_selected_options()
106 }
107 }
108
109 fn get_all_selected_options(&self)->Vec<String>{
110 let mut i = 0;
111 let mut result = vec!();
112 for option in &self.options{
113 if option.selected {
114 result.push(option.name.clone());
115 }
116 i += 1;
117 }
118
119 return result;
120 }
121
122
123 fn get_all_selected_indexies(&self)->Vec<usize>{
124 let mut i = 0;
125 let mut result = vec!();
126 for options in &self.options{
127 if options.selected {
128 result.push(i);
129 }
130 i += 1;
131 }
132
133 return result;
134 }
135
136 fn render_options(&self, stdout: &mut termion::raw::RawTerminal<Stdout>, selector_index: u16, size : (u16, u16)){
137 let mut i = 0;
138
139
140 for option in &self.options{
141 let selector_str = if i == selector_index {
142 let selector = vec!(0xE2, 0x98, 0x9B);
143 String::from_utf8_lossy(&selector).into_owned()
144 }else{
145 " ".to_owned()
146 };
147
148 let selected_icon = if self.options[i as usize].selected {
149 let large_filled_circle = vec!(0xE2, 0xAC, 0xA4);
150 String::from_utf8_lossy(&large_filled_circle).into_owned()
151 }else{
152 let large_circle = vec!(0xE2, 0x97, 0xAF);
153 String::from_utf8_lossy(&large_circle).into_owned()
154 };
155 write!(stdout, "{}{}{}{} {} {}", termion::cursor::Goto(0,size.1 - self.options_len + i), termion::clear::CurrentLine, termion::clear::CurrentLine, selector_str, selected_icon, option.name );
156 i += 1;
157 }
158
159 let selector_str = if i == selector_index {
160 let selector = vec!(0xE2, 0x98, 0x9B);
161 String::from_utf8_lossy(&selector).into_owned()
162 }else{
163 " ".to_owned()
164 };
165 let continue_str = "CONTINUE";
167 write!(stdout, "{}{}{} {}", termion::cursor::Goto(0,size.1 - self.options_len + i), termion::clear::CurrentLine, selector_str, continue_str );
168
169 stdout.flush().unwrap();
170 }
171
172 pub fn add_option_by_str(mut self, option:String)->Self{
195 self.options.push(QuestionOption::new(option));
196 self.options_len += 1;
197 self
198 }
199
200 pub fn add_option(mut self, option: QuestionOption)->Self{
201 self.options.push(option);
202 self.options_len += 1;
203 self
204 }
205
206 }
217
218 pub trait TraitQuestion{
219 fn send(&mut self)->Result<AnswerCollection, String>;
220 }
221
222 pub struct MultipleChoice{
223 message : String,
224 prepended_message : Option<String>,
225 option_groups : Vec<OptionGroup>,
226 validate_fn : Box<Fn(AnswerCollection)->bool>,
227 cursor : Cursor,
228 continue_btn : ContinueBtn,
229 on_select_continue : bool, }
231
232 struct Cursor{
233 on_group : u16,
234 on_option : u16,
235 }
236
237 impl TraitQuestion for MultipleChoice{
238 fn send(&mut self)->Result<AnswerCollection, String>{
239
240 let mut stdout = stdout().into_raw_mode().unwrap();
241 let mut size = termion::terminal_size().unwrap();
242
243 write!(stdout, "{}{}", self.prepended_message.as_ref().unwrap_or(&"".to_owned()), self.message);
245
246 stdout.flush().unwrap();
247
248 let (mut answer_collection, mut line_count) = self.render_options(size);
249
250 let stdin = stdin();
251 for c in stdin.keys(){
252 size = termion::terminal_size().unwrap();
253 match c.unwrap(){
254 Key::Down => {
261 if self.cursor.on_group < self.option_groups.len() as u16 &&
266 self.cursor.on_option + 1 < self.option_groups[self.cursor.on_group as usize].options.len() as u16{
267 self.cursor.on_option += 1;
268 }else if self.cursor.on_group + 1 < self.option_groups.len() as u16{
269 self.cursor.on_group += 1;
270 self.cursor.on_option = 0;
271 }else{
272 self.cursor.on_group = self.option_groups.len() as u16;
273 self.cursor.on_option = 0;
274 }
275 },
276 Key::Up =>{
277 if self.cursor.on_option != 0{
278 self.cursor.on_option -= 1;
279 }else if self.cursor.on_group != 0{
280 self.cursor.on_option = self.option_groups.len() as u16;
281 self.cursor.on_group -= 1;
282 }
283 },
284 Key::Char('\n') =>{
285 if self.cursor.on_group == self.option_groups.len() as u16{ if self.validate(){
288 write!(stdout, "{}{}", termion::scroll::Up(1), termion::cursor::Goto(0,size.1));
289 return Ok(answer_collection);
290 }
291 }else{
292 let group_i = self.cursor.on_group;
293 let option_i = self.cursor.on_option;
294 self.toggle_selected_option(group_i, option_i, None)
295 }
296 },
297 Key::Ctrl('c') => {
298 return Err("Terminated process.".to_string());
299 },
300 _ => {}
304 }
305
306 write!(stdout, "{}", termion::scroll::Down(line_count));
307 let (_answer_collection, _line_count) = self.render_options(size);
308 answer_collection = _answer_collection;
309 line_count = _line_count;
310
311
312 }
313 Ok(answer_collection)
314 }
315 }
316
317 struct ContinueBtn{
318 pub valid_appearance : String,
319 pub invalid_appearance : String,
320 }
321
322 impl ContinueBtn {
323 pub fn get_valid_appearance(&self)->String{
324 self.valid_appearance.clone()
325 }
326
327 pub fn get_invalid_appearance(&self)->String{
328 self.invalid_appearance.clone()
329 }
330
331 pub fn set_valid_appearance(&mut self, new_valid_appearance : String)->&mut Self{
332 self.valid_appearance = new_valid_appearance;
333 self
334 }
335
336 pub fn set_invalid_appearance(&mut self, new_invalid_appearance :String)-> &mut Self{
337 self.invalid_appearance = new_invalid_appearance;
338 self
339 }
340 }
341
342 impl MultipleChoice{
343 pub fn new(message: String) -> MultipleChoice{
344 MultipleChoice{
345 message : message,
346 prepended_message : Some("? ".to_owned()),
347 option_groups : vec!(),
348 validate_fn : Box::new(|_| {true}), on_select_continue : false,
350 cursor : Cursor{
351 on_group: 0,
352 on_option: 0,
353 },
354 continue_btn : ContinueBtn{
355 valid_appearance : "> Continue >".to_owned(),
356 invalid_appearance : "| Continue |".to_owned()
357 }
358 }
359 }
360
361 pub fn replace_continue_btn(&mut self, valid_appearance : String , invalid_appearance : String)->&mut Self{
362 self.continue_btn.valid_appearance = valid_appearance;
363 self.continue_btn.invalid_appearance = invalid_appearance;
364 self
365 }
366
367 pub fn toggle_selected_option(&mut self, group_index: u16, option_index :u16 , io : Option<bool>){
368 if let Some(_io) = io{
369 self.option_groups[group_index as usize].options[option_index as usize].selected = _io;
370 }else{
371 self.option_groups[group_index as usize].options[option_index as usize].selected = !self.option_groups[group_index as usize].options[option_index as usize].selected;
372 }
373 }
374
375 pub fn render_options(&mut self, size : (u16, u16) )->(AnswerCollection, u16){
376 let mut stdout = stdout().into_raw_mode().unwrap();
377 let mut answer_collection = AnswerCollection{answer_sets : vec!()};
378 let mut line_count = 0;
379 let mut curr_grp = 0;
380
381 for grp in &mut self.option_groups{
382 let result = if grp.options.len() < 2{
383 Err("2 or more options must be populated before sending multiple choice.".to_owned())
384 }else{
385 write!(stdout, "{}{} ==== {} ====", termion::scroll::Up(1), termion::cursor::Goto(0,size.1), grp.name);
387 line_count += 1;
388
389 let mut curr_option = 0;
390 for option in & grp.options{
391 let selected_icon = if grp.options[curr_option as usize].selected {
392 let large_filled_circle = vec!(0xE2, 0xAC, 0xA4);
393 String::from_utf8_lossy(&large_filled_circle).into_owned()
394 }else{
395 let large_circle = vec!(0xE2, 0x97, 0xAF);
396 String::from_utf8_lossy(&large_circle).into_owned()
397 };
398
399 let selector_str = if self.cursor.on_group == curr_grp && self.cursor.on_option == curr_option{
400 let selector = vec!(0xE2, 0x98, 0x9B);
401 String::from_utf8_lossy(&selector).into_owned()
402 }else{
403 " ".to_owned()
404 };
405
406 write!(stdout, "{}{}{} {} {}", termion::scroll::Up(1), termion::cursor::Goto(0,size.1), selector_str, selected_icon, option.name);
407 line_count += 1;
408 curr_option += 1;
409 }
410
411
412
413 Ok("".to_owned())
414 };
415
416 if let Err(_error) = result{ }else{ answer_collection.answer_sets.push(grp.to_answerset());
421 }
422 curr_grp += 1;
423 }
424 let is_valid = self.validate();
425 let continue_str = if is_valid {
426 self.continue_btn.get_valid_appearance()
427 }else{
428 self.continue_btn.get_invalid_appearance()
429 };
430
431 let selector_str = if self.cursor.on_group == self.option_groups.len() as u16{
432 let selector = vec!(0xE2, 0x98, 0x9B);
433 String::from_utf8_lossy(&selector).into_owned()
434 }else{
435 " ".to_owned()
436 };
437 write!(stdout, "{}{}{} {}", termion::scroll::Up(1), termion::cursor::Goto(0,size.1), selector_str, continue_str);
438 line_count += 1;
439 stdout.flush().unwrap();
440 return (answer_collection, line_count);
441 }
442 pub fn set_validation(&mut self, f : Box<Fn(AnswerCollection)->bool>)->&mut Self{
444 self.validate_fn = f;
445 self
446 }
447
448 fn validate(&mut self)->bool{
449 let answer_collection = self.to_answer_collections();
450 (self.validate_fn)(answer_collection)
451 }
452
453 fn get_option_group_by_name(&mut self, option_group_name : &'static str)->Option<&mut OptionGroup>{
454 for opt_grp in &mut self.option_groups{
455 if opt_grp.name == option_group_name {
456 return Some(opt_grp)
457 }
458 }
459 None
460 }
461
462 pub fn add_option_group(&mut self, option_group : OptionGroup)->&mut Self{
463 let mut result = None;
464 if let Some(opt_grp) = self.get_option_group_by_name(option_group.name){
465 result = Some(format!("A group with the name \"{}\" already exists under the message", opt_grp.name));
466 }
467
468 if let Some(mut err_msg) = result {
469 err_msg.push_str(&self.message);
470 panic!(err_msg);
471 }else{
472 self.option_groups.push(option_group);
473 }
474 self
475 }
476
477 pub fn to_answer_collections(&mut self)->AnswerCollection{
478 let mut answer_collections = AnswerCollection{
479 answer_sets : vec!()
480 };
481
482 for grp in self.option_groups.iter() {
483 answer_collections.answer_sets.push(grp.to_answerset());
484 }
485 answer_collections
486 }
487 }
488
489 pub struct AnswerCollection{
490 pub answer_sets : Vec<AnswerSet>,
491 }
492
493 impl AnswerSet{
494 pub fn get_first_answer(&mut self)->Option<String>{
495
496 let result = if self.answers.len() > 0{
497 Some(self.answers[0].clone())
498 }else{
499 None
500 };
501
502 result
503 }
504 }
505
506 impl AnswerCollection{
507 fn get_answer_set_by_group_name(&mut self, group_name : &'static str)->Option<& AnswerSet>{
508 let mut result = None;
509 for answer_set in self.answer_sets.iter() {
510 if answer_set.group_name == group_name {
511 result = Some(answer_set);
512 break;
513 }
514 }
515 result
516 }
517
518 pub fn get_first_answer(&mut self)->Option<String>{
519 if self.answer_sets.len() as u16 > 0{
520 self.answer_sets[0].get_first_answer()
521 }else{
522 None
523 }
524 }
525 }
526
527 pub struct AnswerSet{
528 pub group_name : &'static str,
529 pub answers : Vec<String>,
530 }
531
532 pub struct Confirm{
533 message : String,
534 prepended_message : Option<String>
535 }
536
537 impl Confirm{
538 pub fn new(message : String)->Confirm {
539 Confirm{
540 message : message,
541 prepended_message : Some("? ".to_owned())
542 }
543 }
544 }
545
546 impl TraitQuestion for Confirm{
547 fn send(&mut self)->Result<AnswerCollection, String>{
548 let mut stdout = stdout().into_raw_mode().unwrap();
549 let stdin = stdin();
550 let mut size = termion::terminal_size().unwrap();
551 write!(stdout, "{}{} [Y/n] {}{}", self.prepended_message.as_ref().unwrap_or(&"".to_owned()), self.message, termion::scroll::Up(1), termion::cursor::Goto(0, size.1));
552
553 stdout.flush().unwrap();
554 let mut data : Vec<char> = vec!();
555 let yes = Regex::new(r"(?i)^y$|^yes$$").unwrap();
556 let no = Regex::new(r"(?i)^no$|^n$").unwrap();
557 let mut was_unrecognized = false;
558 'outer: for c in stdin.keys(){
559 match c.unwrap(){
561 Key::Char('\n') => {
562 let content = data.iter().cloned().collect::<String>();
563 if yes.is_match(&content) {
564 let answer_collection = AnswerCollection{
565 answer_sets : vec!(AnswerSet{
566 group_name : "default",
567 answers : vec!("yes".to_owned())
568 })
569 };
570 return Ok(answer_collection);
571 }else if no.is_match(&content) {
572 let answer_collection = AnswerCollection{
573 answer_sets : vec!(AnswerSet{
574 group_name : "default",
575 answers : vec!("no".to_owned())
576 })
577 };
578 return Ok(answer_collection);
579
580 }else{
581 write!(stdout, "{}{}unrecognized response.", termion::clear::CurrentLine, termion::cursor::Goto(0, size.1));
582 was_unrecognized = true;
583 data = vec!();
584 stdout.flush().unwrap();
585 }
586 },
587 Key::Char(c) => {
588 data.push(c);
589 if was_unrecognized {
590 write!(stdout, "{}{}{}", termion::cursor::Goto(0, size.1), termion::clear::CurrentLine, c);
591 was_unrecognized = false;
592 }else{
593 write!(stdout,"{}",c);
594 }
595
596 },
597
598 Key::Ctrl('c') => break,
606 _ => {}
608 }
609
610 stdout.flush().unwrap();
611 }
612
613 write!(stdout, "{}{}",termion::cursor::Goto(0, size.1+1), termion::scroll::Up(1)); let content = data.iter().cloned().collect::<String>();
615
616 let answer_collection = AnswerCollection{
617 answer_sets : vec!(AnswerSet{
618 group_name : "default",
619 answers : vec!(content.clone())
620 })
621 };
622
623 Ok(answer_collection)
624 }
626 }
627
628 pub struct OpenEnded{
629 message : String,
630 prepended_message : Option<String>
631 }
632
633 impl OpenEnded{
634 pub fn new(message:String) -> OpenEnded{
635 OpenEnded{
636 message : message,
637 prepended_message : Some("? ".to_owned()),
638 }
639 }
640 }
641
642 impl TraitQuestion for OpenEnded{
643
644 fn send(&mut self)->Result<AnswerCollection, String>{
645 let mut stdout = stdout().into_raw_mode().unwrap();
646 let stdin = stdin();
647 let mut size = termion::terminal_size().unwrap();
648 write!(
649 stdout, "{}{}{}{}",
650 self.prepended_message.as_ref().unwrap_or(&"".to_owned()),
651 self.message,
652 termion::scroll::Up(1),
653 termion::cursor::Goto(0,size.1)
654 );
655
656 stdout.flush().unwrap();
657 let mut data : Vec<char> = vec!();
658 for c in stdin.keys(){
659 match c.unwrap(){
661 Key::Char('\n') => {
662 break;
663 },
664 Key::Char(c) => {
665 data.push(c);
666 write!(stdout,"{}",c);
667 },
668 Key::Ctrl('c') => break,
676 _ => {}
678 }
679
680 stdout.flush().unwrap();
681 }
682
683 write!(stdout, "{}{}",termion::cursor::Goto(0, size.1+1), termion::scroll::Up(1)); let content = data.iter().cloned().collect::<String>();
685
686 let answer_collection = AnswerCollection{
687 answer_sets : vec!(AnswerSet{
688 group_name : "default",
689 answers : vec!(content.clone())
690 })
691 };
692
693 Ok(answer_collection)
694 }
696 }
697
698}