read_human/
lib.rs

1//! Getting data from a human, from stdin.
2//!
3//! This library provides methods for getting information from a human. They all work on buffered
4//! lines of input (this is how terminals work unless you put them in a different mode). They are
5//! useful for building simple interactive command line apps (for example how *pacman* gets
6//! confirmantion during a system upgrade on arch linux). They are also useful for learning, when
7//! you want to be able to get data easily.
8use std::{
9    io::{self, Write},
10    str::FromStr,
11};
12
13/// Get a line of text from the user.
14///
15/// The question is displayed first. This method converts empty text into `None`. Any whitespace
16/// around the input, including the line ending, will be trimmed.
17///
18/// # Examples
19///
20/// ```rust,no_run
21/// # use std::io;
22/// let name = read_human::read_string("Please enter your name: ")?;
23/// if let Some(name) = name {
24///     // An empty string would have been converted into `None`
25///     assert!(name != "");
26/// }
27/// # Ok::<(), io::Error>(())
28/// ```
29pub fn read_string(question: &str) -> io::Result<Option<String>> {
30    print!("{}: ", question);
31    io::stdout().flush()?;
32    let mut buf = String::new();
33    io::stdin().read_line(&mut buf)?;
34    Ok(if buf.trim() == "" {
35        None
36    } else {
37        Some(buf.trim().to_owned())
38    })
39}
40
41/// Get a line of non-empty text from the user.
42///
43/// The question is displayed first. This method converts empty text into `None`. Any whitespace
44/// around the input, including the line ending, will be trimmed.
45///
46/// This function will keep asking for input until it gets a non-empty string.
47///
48/// # Examples
49///
50/// ```rust,no_run
51/// # use std::io;
52/// let name = read_human::read_string_nonempty("Please enter your name: ")?;
53/// // The function will not return until we have a non-empty string
54/// assert!(name != "");
55/// # Ok::<(), io::Error>(())
56/// ```
57pub fn read_string_nonempty(question: &str) -> io::Result<String> {
58    loop {
59        match read_string(question)? {
60            Some(s) => return Ok(s),
61            None => println!("Input must not be empty."),
62        };
63    }
64}
65
66/// Get a line of from the user without displaying a question first.
67///
68/// This method converts empty text into `None`. Any whitespace
69/// around the input, including the line ending, will be trimmed.
70///
71/// # Examples
72///
73/// ```rust,no_run
74/// # use std::io;
75/// let name = read_human::read_string_noquestion()?;
76/// if let Some(name) = name {
77///     // An empty string would have been converted into `None`
78///     assert!(name != "");
79/// }
80/// # Ok::<(), io::Error>(())
81/// ```
82pub fn read_string_noquestion() -> io::Result<Option<String>> {
83    io::stdout().flush()?;
84    let mut buf = String::new();
85    io::stdin().read_line(&mut buf)?;
86    Ok(if buf.trim() == "" {
87        None
88    } else {
89        Some(buf.trim().to_owned())
90    })
91}
92
93/// Allow the user to choose between a set of choices, and optionally give them a default choice.
94///
95/// The default will be selected if the user just hits enter. If the user writes something that's
96/// not one of the choices, the function will ask for another line of input.
97///
98/// # Panics
99///
100/// This function will panic if the default value is >= the number of options.
101///
102/// # Examples
103///
104/// ```rust,no_run
105/// # use std::io;
106/// let choice = read_human::read_choice("What is your gender?", &["male", "female"], None)?;
107/// // Must be 0 (male) or 1 (female)
108/// assert!(choice <= 2);
109/// # Ok::<(), io::Error>(())
110/// ```
111pub fn read_choice(
112    question: &str,
113    options: &[impl AsRef<str>],
114    default: Option<usize>,
115) -> io::Result<usize> {
116    assert!(
117        if let Some(val) = default {
118            val < options.len()
119        } else {
120            true
121        },
122        "default index must be in the options slice"
123    );
124    loop {
125        print!("{} [", question);
126        let mut options_iter = options.iter().enumerate();
127        if let Some((_, opt)) = options_iter.next() {
128            print!(r#"1: "{}""#, opt.as_ref());
129        }
130        for (idx, option) in options_iter {
131            print!(r#", {}: "{}""#, idx + 1, option.as_ref());
132        }
133        if let Some(d) = default {
134            print!(" (default: {})", d + 1);
135        }
136        print!("]: ");
137        io::stdout().flush()?;
138        let mut buf = String::new();
139        io::stdin().read_line(&mut buf)?;
140        let ans = buf.trim();
141        if let Some(val) = default {
142            if ans == "" {
143                return Ok(val);
144            }
145        }
146        match ans.parse::<usize>() {
147            Ok(a) => {
148                let ans = a - 1;
149                if ans < options.len() {
150                    return Ok(ans);
151                } else {
152                    println!("{} is not a valid option (too big)", ans);
153                }
154            }
155            Err(_) => {
156                println!("{} is not a valid option", ans);
157            }
158        }
159    }
160}
161
162/// Read in any type that implementd `FromStr` from stdio.
163///
164/// If the text was empty, or couldn't be converted, then the user will be asked for more input.
165///
166/// # Examples
167///
168/// ```rust,no_run
169/// # use std::io;
170/// let number: u32 = read_human::read_custom_nonempty("How old are you")?;
171/// # Ok::<(), io::Error>(())
172/// ```
173pub fn read_custom_nonempty<T: FromStr>(question: &str) -> io::Result<T> {
174    loop {
175        let raw = read_string_nonempty(question)?;
176        match raw.parse::<T>() {
177            Ok(t) => return Ok(t),
178            Err(_) => println!("{} is not valid", raw),
179        }
180    }
181}
182
183/// Read in any type that implementd `FromStr` from stdio.
184///
185/// If the text couldn't be converted, then the user will be asked for more input.
186///
187/// # Examples
188///
189/// ```rust,no_run
190/// # use std::io;
191/// let number: Option<u32> = read_human::read_custom(
192///     "Let us know how many times you've visited, if applicable")?;
193/// # Ok::<(), io::Error>(())
194/// ```
195pub fn read_custom<T: FromStr>(question: &str) -> io::Result<Option<T>> {
196    loop {
197        let raw = match read_string(question)? {
198            Some(s) => s,
199            None => return Ok(None),
200        };
201        match raw.parse::<T>() {
202            Ok(t) => return Ok(Some(t)),
203            Err(_) => println!("{} is not valid", raw),
204        }
205    }
206}
207
208/// Read in any type that implementd `FromStr` from stdio.
209///
210/// If the text couldn't be converted, then the user will be asked for more input.
211///
212/// # Examples
213///
214/// ```rust,no_run
215/// # use std::io;
216/// let number: Option<u32> = read_human::read_custom_noquestion()?;
217/// # Ok::<(), io::Error>(())
218/// ```
219pub fn read_custom_noquestion<T: FromStr>() -> io::Result<Option<T>> {
220    loop {
221        let raw = match read_string_noquestion()? {
222            Some(s) => s,
223            None => return Ok(None),
224        };
225        match raw.parse::<T>() {
226            Ok(t) => return Ok(Some(t)),
227            Err(_) => println!("{} is not valid", raw),
228        }
229    }
230}