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
//! Functions and macros that deal with the terminal // std uses use std::io::{stdin, stdout, Write}; use std::process::Command; // Flushes stdout, this is only used internally fn flush() { stdout().flush().expect("Failed to flush output"); } /// Calls [`prompt`](./helpers/cli/fn.prompt.html), then tries to parse the input /// into the provided type. If parsing fails, it will try again. /// /// This method trims whitespace on the beginning and end of the input string. /// /// ## Example /// ```no_run /// #[macro_use] extern crate rubric; /// use rubric::helpers; /// use std::net::Ipv4Addr; /// /// fn main() { /// let string = prompt!("Enter a string: ", String); /// println!("{}", string); /// /// let number = prompt!("Enter a number: ", u32); /// println!("{}", number); /// /// let another = prompt!("Enter an IP: ", Ipv4Addr); /// println!("{}", another); /// } /// ``` /// They input: /// ```text /// Enter a string: Here's a string /// Here's a string /// Enter a number: 123 /// 123 /// Enter an IP: not an IP /// Could not parse input. Try again. /// Enter an IP: 192.168.0.1 /// 192.168.0.1 /// ``` #[macro_export] macro_rules! prompt { ( $msg:expr, $type:ty ) => { loop { match rubric::helpers::cli::prompt($msg).parse::<$type>() { Ok(val) => break val, Err(_) => { eprintln!("Could not parse input. Try again."); } }; }; }; } /// Prompts a user for input from the CLI. /// /// Returns the string they entered, with leading and trailing whitespace trimmed. /// This method will loop infinitely until a valid string is read. /// /// If you're going to cast the result to a certain type, try the /// [`prompt!`](../../macro.prompt.html) macro. /// /// ## Example /// ```no_run /// use rubric::helpers::cli::prompt; /// /// let input = prompt("Enter hello: "); /// println!("{}", input); /// ``` /// They see: /// ```text /// Enter hello: hello /// hello /// ``` pub fn prompt(msg: &str) -> String { let mut input = String::new(); loop { print!("{}", msg); flush(); if let Err(e) = stdin().read_line(&mut input) { println!("Error: {}", e); println!("Try again."); flush(); } else { return input.trim().to_string(); } } } /// Prints the message, then returns true if the user enters y/yes, false otherwise. /// /// This method will append `[y/N]` to the message. /// /// ```no_run /// use rubric::helpers::cli; /// /// if cli::confirm("Are you sure?") { /// // they entered y /// } else { /// // they entered anything else /// } /// ``` pub fn confirm(message: &str) -> bool { let full_msg = format!("{} [y/N] ", message); return ["Y", "YES", "SI"].contains(&prompt(&full_msg).to_uppercase().as_str()); } /// Runs a command and returns a Result with the output. This is the Windows version. /// /// This is equivilent to using [`Command`](std::process::Command), but it /// handles platform differences for you. This is only meant for basic commands. For /// anything more advanced than a simple command, use [`Command`](std::process::Command) /// yourself. /// /// ```rust /// use rubric::helpers::cli; /// /// let output = cli::cmd("dir"); /// assert!(output.is_ok()); /// assert!(output.unwrap().stdout.len() > 0); /// ``` #[cfg(target_family = "windows")] pub fn cmd(command: &str) -> std::result::Result<std::process::Output, std::io::Error> { Command::new("cmd") .args(&["/C", command]) .output() } /// Runs a command and returns a Result with the output. This is the Unix version. /// /// This is equivilent to using [`Command`](std::process::Command), but it /// handles platform differences for you. This is only meant for basic commands. For /// anything more advanced than a simple command, use [`Command`](std::process::Command) /// yourself. /// /// ```rust /// use rubric::helpers::cli; /// /// let output = cli::cmd("ls"); /// assert!(output.is_ok()); /// assert!(output.unwrap().stdout.len() > 0); /// ``` #[cfg(target_family = "unix")] pub fn cmd(command: &str) { Command::new("sh") .arg("-c") .arg(command) .output() } #[cfg(test)] mod tests { use super::*; #[test] #[cfg(target_family = "windows")] fn test_windows_command() { let result = cmd("dir"); assert!(result.is_ok()); let output = result.unwrap(); assert!(output.stdout.len() > 0); assert!(output.stderr.len() == 0); assert!(output.status.success()); } #[test] #[cfg(target_family = "unix")] fn test_unix_command() { let result = cmd("ls"); assert!(result.is_ok()); let output = result.unwrap(); assert!(output.stdout.len() > 0); assert!(output.stderr.len() == 0); assert!(output.status.success()); } }