1#![crate_type = "lib"]
2#![allow(clippy::borrowed_box)]
4use help::CliHelpScreen;
17pub use indexmap::{indexmap, IndexMap};
18pub use textwrap::Options as Textwrap_Options;
19pub use textwrap::fill as Textwrap_Fill;
20use router::CliRouter;
21use rpassword::read_password;
22use std::collections::HashMap;
23pub use std::io;
24pub use std::io::Write;
25use zxcvbn::zxcvbn;
26
27pub mod help;
28pub mod router;
29
30pub trait CliCommand {
31 fn process(&self, args: Vec<String>, flags: Vec<String>, value_flags: HashMap<String, String>);
32 fn help(&self) -> CliHelpScreen;
33}
34
35pub fn cli_run(router: &CliRouter) {
37 let (cmd, req) = router.lookup();
39
40 if req.is_help {
42 CliHelpScreen::render(cmd, &req.cmd_alias, &req.shortcuts);
43 } else {
44 cmd.process(req.args, req.flags, req.value_flags);
45 }
46}
47
48pub fn cli_header(text: &str) {
50 println!("------------------------------");
51 println!("-- {}", text);
52 println!("------------------------------\n");
53}
54
55#[macro_export]
57macro_rules! cli_send {
58 ($text:expr) => {
59 let wrapped_text = Textwrap_Fill($text, Textwrap_Options::new(75));
60 print!("{}", wrapped_text);
61 io::stdout().flush().unwrap();
62 };
63 ($text:expr, $( $arg:expr ),*) => {
64
65 let mut args = vec![];
67 $( args.push($arg.to_string()); )*
68
69 let mut text: String = $text.to_string();
71 for arg in args {
72 text = text.replacen("{}", arg.to_string().as_str(), 1);
73 }
74
75 let wrapped_text = Textwrap_Fill(text.as_str(), Textwrap_Options::new(75));
77 print!("{}", wrapped_text);
78 io::stdout().flush().unwrap();
79 };
80}
81
82
83pub fn cli_get_option(question: &str, options: &IndexMap<String, String>) -> String {
85 let message = format!("{}\r\n\r\n", question);
86 cli_send!(&message);
87 for (key, value) in options.iter() {
88 let line = format!(" [{}] {}\r\n", key, value);
89 cli_send!(&line);
90 }
91 cli_send!("\r\nSelect One: ");
92
93 let mut input: String;
95 loop {
96 input = String::new();
97
98 io::stdin()
99 .read_line(&mut input)
100 .expect("Failed to read line");
101 let input = input.trim();
102 if !options.contains_key(input) {
103 print!("\r\nInvalid option, try again: ");
104 io::stdout().flush().unwrap();
105 } else {
106 break;
107 }
108 }
109
110 input.trim().to_string()
111}
112
113pub fn cli_get_input(message: &str, default_value: &str) -> String {
115 cli_send!(message);
117 io::stdout().flush().unwrap();
118
119 let mut input = String::new();
121 io::stdin()
122 .read_line(&mut input)
123 .expect("Failed to read line");
124 let mut input = input.trim();
125
126 if input.trim().is_empty() {
128 input = default_value;
129 }
130
131 String::from(input)
132}
133
134pub fn cli_confirm(message: &str) -> bool {
136 let confirm_message = format!("{} (y/n): ", message);
138 cli_send!(&confirm_message);
139
140 let mut _input = "".to_string();
142 loop {
143 _input = String::new();
144
145 io::stdin()
146 .read_line(&mut _input)
147 .expect("Failed to read line");
148 let _input = _input.trim().to_lowercase();
149
150 if _input != "y" && _input != "n" {
151 cli_send!("Invalid option, please try again. Enter (y/n): ");
152 } else {
153 break;
154 }
155 }
156
157 let res_char = _input.chars().next().unwrap();
159
160 res_char == 'y'
161}
162
163pub fn cli_get_password(message: &str) -> String {
165 let password_message = if message.is_empty() {
167 "Password: "
168 } else {
169 message
170 };
171
172 let mut _password = String::new();
174 loop {
175 cli_send!(password_message);
176 _password = read_password().unwrap();
177
178 if _password.is_empty() {
179 cli_send!("You did not specify a password");
180 } else {
181 break;
182 }
183 }
184
185 _password
186}
187
188pub fn cli_get_new_password(req_strength: u8) -> String {
190 let mut _password = String::new();
192 let mut _confirm_password = String::new();
193
194 loop {
196 cli_send!("Desired Password: ");
197 _password = read_password().unwrap();
198
199 if _password.is_empty() {
200 cli_send!("You did not specify a password");
201 continue;
202 }
203
204 let strength = zxcvbn(&_password, &[]).unwrap();
206 if strength.score() < req_strength {
207 cli_send!("Password is not strong enough. Please try again.\n\n");
208 continue;
209 }
210
211 cli_send!("Confirm Password: ");
213 _confirm_password = read_password().unwrap();
214 if _password != _confirm_password {
215 cli_send!("Passwords do not match, please try again.\n\n");
216 continue;
217 }
218 break;
219 }
220
221 _password
222}
223
224pub fn cli_display_table(columns: Vec<&str>, rows: Vec<Vec<&str>>) {
226 if rows.is_empty() {
228 cli_send!("No rows to display.\n\n");
229 return;
230 }
231
232 let mut sizes: HashMap<&str, usize> = HashMap::new();
234 for col in columns.as_slice() {
235 sizes.insert(col, 0);
236 }
237
238 for _row in rows.clone() {
240 for col in columns.as_slice() {
241 let num: usize = col.len();
242 if num > sizes[col] {
243 sizes.insert(col, num + 3);
244 }
245 }
246 }
247
248 let mut header = String::from("+");
250 let mut col_header = String::from("|");
251
252 for col in columns.clone() {
254 let padded_col = format!("{}{}", col, " ".repeat(sizes[col] - col.len()));
255 header = header + "-".repeat(sizes[col] + 1).as_str() + "+";
256 col_header += format!(" {}|", padded_col).as_str();
257 }
258 println!("{}\n{}\n{}", header, col_header, header);
259
260 for row in rows {
262 let mut line = String::from("|");
264 for (i, val) in row.into_iter().enumerate() {
265 let padded_val = format!(" {}{}", val, " ".repeat(sizes[columns[i]] - val.len()));
266 line += format!("{}|", padded_val).as_str();
267 }
268 println!("{}", line);
269 }
270 println!("{}\n", header);
271}
272
273pub fn cli_display_array(rows: &IndexMap<String, String>) {
275 let mut size = 0;
277 for key in rows.keys() {
278 if key.len() + 8 > size {
279 size = key.len() + 8;
280 }
281 }
282 let indent = " ".repeat(size);
283 let indent_size = size - 4;
284
285 for (key, value) in rows {
287 let left_col = format!(" {}{}", key, " ".repeat(indent_size - key.len()));
288 let options = textwrap::Options::new(75)
289 .initial_indent(&left_col)
290 .subsequent_indent(&indent);
291 let line = textwrap::fill(value, &options);
292 println!("{}", line);
293 }
294 cli_send!("\r\n");
295}
296
297#[macro_export]
299macro_rules! cli_error {
300 ($text:expr) => {
301 let wrapped_text = Textwrap_Fill(format!("ERROR: {}", $text).as_str(), Textwrap_Options::new(75));
302 print!("{}\r\n", wrapped_text);
303 io::stdout().flush().unwrap();
304 };
305 ($text:expr, $( $arg:expr ),*) => {
306
307 let mut args = vec![];
309 $( args.push($arg.to_string()); )*
310
311 let mut text: String = $text.to_string();
313 for arg in args {
314 text = text.replacen("{}", arg.to_string().as_str(), 1);
315 }
316
317 let wrapped_text = Textwrap_Fill(format!("ERROR: {}", text).as_str(), Textwrap_Options::new(75));
319 print!("{}\r\n", wrapped_text);
320 io::stdout().flush().unwrap();
321 };
322}
323
324
325pub fn cli_success (message: &str, indented_lines: Vec<&str>) {
327 cli_send!(&message);
328 cli_send!("\r\n");
329 for line in indented_lines {
330 println!(" {}", line);
331 }
332 cli_send!("\r\n");
333}
334
335
336pub fn cli_clear_screen() {
338 print!("\x1B[2J");
339}