1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
//! # uQuery
//!
//! A simple way to create beautiful command line experiences.
//! uQuery allows you both to query the user for data, and display user errors and warnings
//!
//! ## Example
//! ```
//! fn main() {
//! 	uquery::notice("Cli registration form", 1);
//! 	// Asks the user for there favourite food
//! 	let favourite_food = uquery::string("What us your favourite food");
//!
//! 	// Asks the user for their favourite number
//! 	let favourite_number = uquery::integer("What is your favourite number");
//!
//! 	// Asks the user for their name, defaulting to John Doe
//! 	let name = uquery::string_with_default("What is your name", "John Doe");
//!
//!		// Asks the user for comfirmation
//! 	match uquery::boolean("Are you sure you have entered the correct details") {
//!			true => {
//!				// Tells the user that we are creating a user
//!				uquery::notice("Creating  User", 3);
//!
//!				// Prints a mesage about them
//!				println!(
//!					"Welcome {}, You will be prepared {} {}s ",
//!					name,
//!					favourite_food,
//!					favourite_number
//!				);
//!			}
//!			false => {
//!				// Warns the user of not completing the form
//!				uquery::warning(
//!					"You have quit the form, you will now not be registered",
//!					"Redo the form"
//!				);
//!			}
//! 	}
//! }
//! ```

#![warn(missing_docs)]
use colored::*;
use std::io::Write;

/// Queries the user input.
///
/// This does not trim the output but the last newline is removed
pub fn query_untrimmed(string: String) -> String {
    // Prints the String
    print!("{} ", string.to_string().trim());
    std::io::stdout().flush().expect("Error Flushing Stdout");

    // Creates a String to store the users input
    let mut input = String::new();

    // Reads from the standard input.
    std::io::stdin().read_line(&mut input).expect("I/O Error");
    // Returns the user's input.
    input[0..(input.len() - 1)].to_string()
}

/// Same as query_untrimmed, but this time the result removes leading and trailing spaces
pub fn query(string: String) -> String {
    query_untrimmed(string).trim().to_string()
}

/// Queries the user for string input.
pub fn string(string: &str) -> String {
    string_with_default(string, None)
}

/// Queries the user for string input with a default.
///
///  The user can unset the value by typing a space,
/// otherwise, if no string is specified and a deafult is given the default will be used.
pub fn string_with_default(string: &str, def: Option<String>) -> String {
	string_with_default_reference(string, def.as_ref())
}

/// Queries the user for string input with a default.
///
///  The user can unset the value by typing a space,
/// otherwise, if no string is specified and a deafult is given the default will be used.
pub fn string_with_default_reference(string: &str, def: Option<&String>) -> String {
    let string = query_untrimmed(format!(
        "{}{} {}:",
        "::".yellow(),
        {
            match def {
                Some(ref def) => {
                    if def.is_empty() {
                        "".to_string()
                    } else {
                        format!(" (Defaulting to {})", def.green())
                    }
                }
                None => "".to_string(),
            }
        },
        string.normal()
    ));
    if string.is_empty() && def != None {
        return def.unwrap().to_string();
    }
    // This ensures that we can unset the value with a space
    string.trim().to_string()
}

/// Queries the user for integer input.
///
/// This requires the user gives a valid integer or it will ask again.
pub fn int(string: &str) -> i64 {
    int_with_default(string, None)
}

/// Queries the user for integer input.
///
/// This time a default is given which cannot be unset unless another value is given
pub fn int_with_default(string: &str, int: Option<i64>) -> i64 {
    loop {
        let string = query_untrimmed(format!(
            "{}{} {}:",
            "::".cyan(),
            {
                match int {
                    None => "".to_string(),
                    Some(int) => {
                        format!(" (Defaulting to {})", int.to_string().green())
                    }
                }
            },
            string.normal()
        ));
        if int != None && string.is_empty() {
            return int.unwrap();
        }
        match string.parse::<i64>() {
            Ok(n) => return n,
            Err(_e) => println!("Please Enter a Valid Number"),
        }
    }
}

/// Queries the user for boolean input.
///
/// If no response is given a true is implied.
/// If this is not the desired behavour than one can use boolean_with_default.
pub fn bool(string: &str) -> bool {
    boolean_with_default(string, Some(true))
}

/// Queries the user for boolean input.
///
/// If no default is given the user will only be allowed past if the value given begins with a 'y' or a 'n'.
/// Otherwise, they can specify either true or false as the default.
pub fn boolean_with_default(string: &str, def: Option<bool>) -> bool {
    loop {
        let string = query(format!(
            "{} {} {} {} ",
            "::".magenta(),
            string.normal(),
            {
                match def {
                    Some(def) => match def {
                        true => "[Y/n]".green(),
                        false => "[y/N]".green(),
                    },
                    None => "(y/n)".green(),
                }
            },
            "".normal()
        ));
        let first_char_op = string.get(0..1);
        match first_char_op {
            // Checks if we have a response
            Some(first_char_op) => {
                // Checks if we get a yes.
                if first_char_op == "Y" || first_char_op == "y" {
                    return true;
                }
                // Checks if we get a no.
                if first_char_op == "N" || first_char_op == "n" {
                    return false;
                }
                println!("Please Enter a Valid Boolean.")
            }
            // Never Actually Happens
            None => return true,
        }
    }
}

/// Notifies the user through a title.
///
///  The level can be one of four values:
///
/// 1. Full title
/// 2. Sub title
/// 3. Notice
/// 4. Small Notice
///
/// The string should be relatively short, if it is too long then the titles may wrap around multiple lines
/// and look buggy.
pub fn notice(string: &str, level: u8) {
    println!();
    match level {
        1 => {
            if let Some((w, _)) = term_size::dimensions() {
                println!(
                    "{}{}",
                    " ".repeat((w / 2) - (string.len() / 2) - 2),
                    "-".repeat(string.len() + 4)
                );
                println!(
                    "{}| {} |{}",
                    "=".repeat((w / 2) - (string.len() / 2) - 2).green(),
                    string.bold(),
                    "=".repeat((w / 2) - (string.len() / 2) - 2).green()
                );
                println!(
                    "{}{}",
                    " ".repeat((w / 2) - (string.len() / 2) - 2),
                    "-".repeat(string.len() + 4)
                );
            } else {
                println!(
                    "{}| {} |{}",
                    "=====".green(),
                    string.bold(),
                    "=====".green()
                )
            }
        }
        2 => {
            if let Some((w, _)) = term_size::dimensions() {
                println!(
                    "{}{}{}{}{}",
                    " ".repeat(w / 4),
                    "-".repeat((w / 4) - std::cmp::min(string.len() / 2, w / 4))
                        .green(),
                    string,
                    "-".repeat((w / 4) - std::cmp::min(string.len() / 2, w / 4))
                        .green(),
                    " ".repeat(w / 4),
                );
            } else {
                println!("{}| {} |{}", "  -----".green(), string, "-----  ".green())
            }
        }
        3 => {
            if let Some((w, _)) = term_size::dimensions() {
                println!(
                    "{} {} {}",
                    "----".green(),
                    string,
                    "-".repeat((w / 4) - std::cmp::min(string.len() / 2, w / 4))
                        .green(),
                );
            } else {
                println!("{} {} {}", "---".green(), string, "-----  ".green())
            }
        }
        4 => {
            println!("{}", string.green());
        }
        _ => {}
    }
    println!();
}

/// Displays an error to the user.
///
///  This **is not** designed for program-side error,
/// as it prints in colour to the standard output, not the stderr. It also lets you give
/// suggestions, so the user can know what they have done wrong.
pub fn error(string: &str, suggestion: Option<&str>) {
    match suggestion {
        Some(suggestion) => {
            println!(" {} {} {}", "|".cyan(), "Error:".red(), string);
            println!(" {} {} {}", "|".cyan(), "Suggestion:".cyan(), suggestion);
        }
        None => {
            println!("{} {}", "Error:".red(), string);
        }
    }
}

/// Displays a warning to the user.
///
/// This **is not** designed for program-side error,
/// as it prints in colour to the standard output, not the stderr. It also lets you give
/// suggestions, so the user can know what they have done wrong.
pub fn warning(string: &str, suggestion: Option<&str>) {
    match suggestion {
        Some(suggestion) => {
            println!(" | {} {}", "Error:".yellow(), string);
            println!(" | {} {}", "Suggestion:".cyan(), suggestion);
        }
        None => {
            println!("{} {}", "Error:".yellow(), string);
        }
    }
}