zen_console_input/
input.rs

1//! Provides functionality for getting text input from the user.
2
3use std::io::{self, Write};
4use std::str::FromStr;
5
6/// Struct for handling text input from the user.
7pub struct Input {
8    message: String,
9    default: Option<String>,
10    tips: bool,
11    multiline: bool,
12}
13
14impl Input {
15    pub(crate) fn new() -> Self {
16        Input {
17            message: String::new(),
18            default: None,
19            tips: true,
20            multiline: false,
21        }
22    }
23
24    /// Sets the prompt message for the input.
25    pub fn message(mut self, msg: &str) -> Self {
26        self.message = msg.to_string();
27        self
28    }
29
30    /// Sets a default value for the input.
31    pub fn default(mut self, value: &str) -> Self {
32        self.default = Some(value.to_string());
33        self
34    }
35
36    /// Disables tips for the input.
37    pub fn disable_tips(mut self) -> Self {
38        self.tips = false;
39        self
40    }
41
42    /// Enables multiline input mode.
43    pub fn multiline(mut self) -> Self {
44        self.multiline = true;
45        self
46    }
47
48    /// Gets the input from the user as a String.
49    pub fn get_input(&self) -> String {
50        if self.multiline {
51            self.get_multiline_input()
52        } else {
53            self.get_single_line_input()
54        }
55    }
56
57    /// Gets the input from the user and parses it to the specified type.
58    pub fn get_parsed_input<T: FromStr>(&self) -> T
59    where
60        <T as FromStr>::Err: std::fmt::Debug,
61    {
62        loop {
63            let input = self.get_input();
64            match input.parse() {
65                Ok(value) => return value,
66                Err(_) => {
67                    eprintln!("Invalid input. Please try again.");
68                    continue;
69                }
70            }
71        }
72    }
73
74    fn get_single_line_input(&self) -> String {
75        print!("{}", self.message);
76        io::stdout().flush().unwrap();
77
78        let mut input = String::new();
79        io::stdin().read_line(&mut input).unwrap();
80        let input = input.trim().to_string();
81
82        if input.is_empty() && self.default.is_some() {
83            self.default.as_ref().unwrap().clone()
84        } else {
85            input
86        }
87    }
88
89    fn get_multiline_input(&self) -> String {
90        println!("{}", self.message);
91        if self.tips {
92            print!("(Press Enter, then Ctrl+D to send)");
93            #[cfg(feature = "editor")]
94            print!(" (In a blank line type @e, then press Enter to edit in external editor)");
95            println!("");
96        }
97
98        let mut input = String::new();
99        loop {
100            let mut line = String::new();
101            let bytes_read = io::stdin().read_line(&mut line);
102
103            match bytes_read {
104                Ok(0) => break, // EOF (Ctrl-D)
105                Ok(_) => {
106                    #[cfg(feature = "editor")]
107                    if line.trim() == "@e" {
108                        match super::ZenConsoleInput::edit_in_external_editor(&input) {
109                            Ok(edited_content) => {
110                                input = edited_content;
111                                break;
112                            }
113                            Err(e) => {
114                                println!("Error using external editor: {}", e);
115                            }
116                        }
117                    }
118                    input.push_str(&line);
119                }
120                Err(e) => {
121                    eprintln!("Error reading input: {}", e);
122                    break;
123                }
124            }
125        }
126
127        if input.trim().is_empty() && self.default.is_some() {
128            self.default.as_ref().unwrap().clone()
129        } else {
130            input.trim().to_string()
131        }
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn test_input_with_default() {
141        let input = Input::new().message("Enter your name").default("John Doe");
142        assert!(input.message.contains("Enter your name"));
143        assert_eq!(input.default, Some("John Doe".to_string()));
144    }
145
146    #[test]
147    fn test_multiline_input() {
148        let input = Input::new().message("Enter your bio").multiline();
149        assert!(input.multiline);
150    }
151}