1#![deny(missing_docs)]
2
3#[macro_use] extern crate cfg_if;
6extern crate rpassword;
7extern crate tempfile;
8#[cfg(unix)] extern crate termios;
9#[cfg(windows)] extern crate winapi;
10
11use interactive::Interactive;
12use std::ffi::OsStr;
13use std::fs::File;
14use std::io::{Read, Write};
15use std::process::Command;
16use std::{env, io};
17use tempfile::TempDir;
18
19mod interactive;
20mod util;
21
22const ESC: u8 = 0x1B;
24
25pub fn ask(q: &str) {
27 print!("\u{1B}[1m{}\u{1B}[0m", q);
28 io::stdout().flush().unwrap();
29}
30
31pub fn success(s: &str) {
33 println!("\u{1B}[1;92m{}\u{1B}[0m", s);
34}
35
36pub fn error(s: &str) {
38 println!("\u{1B}[1;91m{}\u{1B}[0m", s);
39}
40
41pub fn password() -> io::Result<String> {
43 rpassword::read_password()
44}
45
46pub fn text() -> io::Result<String> {
48 let mut out = String::new();
51 io::stdin().read_line(&mut out)?;
52
53 if let Some(mut newline) = out.find('\n') {
56 if newline > 0 && out.as_bytes()[newline - 1] == b'\r' { newline -= 1; }
57 out.truncate(newline);
58 }
59
60 Ok(out)
61}
62
63pub fn yesno(default: bool) -> io::Result<Option<bool>> {
67 let s = text()?.to_lowercase();
68 Ok(if s.is_empty() {
69 Some(default)
70 } else if "yes".starts_with(&s) {
71 Some(true)
72 } else if "no".starts_with(&s) {
73 Some(false)
74 } else {
75 None
76 })
77}
78
79pub fn editor(name: &str, message: &[u8]) -> io::Result<String> {
88 let dir = TempDir::new()?;
91 let path = dir.path().join(name);
92 File::create(&path)?.write_all(message)?;
93
94 let editor = env::var_os("VISUAL").or_else(|| env::var_os("EDITOR"));
97
98 let editor = match editor {
99 Some(ref editor) => editor,
100 None => OsStr::new(
101 #[cfg(windows)] "notepad",
102 #[cfg(unix)] "vi"),
103 };
104
105 Command::new(editor).arg(&path).spawn()?.wait()?;
108
109 let mut out = String::new();
112 File::open(&path)?.read_to_string(&mut out)?;
113 Ok(out)
114}
115
116#[derive(Clone, Copy, Debug)]
118pub struct Boxes<'a> {
119 pub on: &'a str,
121 pub off: &'a str,
123}
124
125impl<'a> Default for Boxes<'a> {
126 fn default() -> Self {
127 Self {
128 on: ">",
129 off: " ",
130 }
131 }
132}
133
134pub fn choose<S: AsRef<str>>(boxes: Boxes, items: &[S]) -> io::Result<usize> {
136 assert!(items.len() > 0);
137
138 let stdin = io::stdin();
139 let mut stdin = stdin.bytes();
140 let mut selected = 0;
141
142 let interactive = Interactive::start()?;
143
144 loop {
145 for (i, item) in items.iter().enumerate() {
146 println!(
147 "{} {}",
148 if i == selected { boxes.on } else { boxes.off },
149 item.as_ref(),
150 );
151 }
152
153 match util::or2ro(stdin.next())? {
154 Some(ESC) => match util::or2ro(stdin.next())? {
155 Some(b'[') => match util::or2ro(stdin.next())? {
156 Some(b'A') => {
157 selected = selected.saturating_sub(1);
158 }
159 Some(b'B') => {
160 selected = selected.saturating_add(1).min(items.len() - 1);
161 }
162 None => break,
163 Some(_) => (),
164 },
165 None => break,
166 Some(_) => (),
167 },
168 Some(b'\r') | Some(b'\n') => break,
169 Some(b'k') => {
170 selected = selected.saturating_sub(1);
171 }
172 Some(b'j') => {
173 selected = selected.saturating_add(1).min(items.len() - 1);
174 }
175 None => break,
176 Some(_) => (),
177 }
178
179 interactive.up(items.len());
180 interactive.clear_right();
181 }
182
183 Ok(selected)
184}