extern crate tempdir;
use std::fmt;
use std::ascii::AsciiExt;
use std::io;
use std::str;
use std::process;
use std::io::{Read, Write};
use std::collections::HashMap;
use std::path::Path;
use std::fs::File;
use std::time::{Instant, Duration, SystemTime, UNIX_EPOCH};
use self::tempdir::TempDir;
use libc;
use libc::ioctl;
pub use self::Color::{
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White
};
pub enum Color {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White
}
impl Color {
pub fn get_fg_str(&self) -> &str {
match *self {
Black => "\x1b[30m",
Red => "\x1b[31m",
Green => "\x1b[32m",
Yellow => "\x1b[33m",
Blue => "\x1b[34m",
Magenta => "\x1b[35m",
Cyan => "\x1b[36m",
White => "\x1b[37m",
}
}
pub fn get_bg_str(&self) -> &str {
match *self {
Black => "\x1b[40m",
Red => "\x1b[41m",
Green => "\x1b[42m",
Yellow => "\x1b[43m",
Blue => "\x1b[44m",
Magenta => "\x1b[45m",
Cyan => "\x1b[46m",
White => "\x1b[47m",
}
}
}
pub struct Style {
text: String,
fg: Option<Color>,
bg: Option<Color>,
bold: Option<bool>,
dim: Option<bool>,
underline: Option<bool>,
blink: Option<bool>,
reverse: Option<bool>,
}
impl Style {
pub fn new(text: String) -> Style {
Style {
text: text,
fg: None,
bg: None,
bold: None,
dim: None,
underline: None,
blink: None,
reverse: None,
}
}
pub fn fg(&mut self, color: Color) {
self.fg = Some(color);
}
pub fn bg(&mut self, color: Color) {
self.bg = Some(color);
}
pub fn bold(&mut self, bold: bool) {
self.bold = Some(bold);
}
pub fn dim(&mut self, dim: bool) {
self.dim = Some(dim);
}
pub fn underline(&mut self, underline: bool) {
self.underline = Some(underline);
}
pub fn blink(&mut self, blink: bool) {
self.blink = Some(blink);
}
pub fn reverse(&mut self, reverse: bool) {
self.reverse = Some(reverse);
}
}
impl fmt::Display for Style {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self.fg {
Some(ref fg) => {
try!(f.write_str(fg.get_fg_str()));
},
None => ()
}
match self.bg {
Some(ref bg) => {
try!(f.write_str(bg.get_bg_str()));
},
None => ()
}
if self.bold.is_some() {
if self.bold.unwrap() {
try!(f.write_str("\x1b[1m"));
} else {
try!(f.write_str("\x1b[22m"));
}
}
if self.dim.is_some() {
if self.dim.unwrap() {
try!(f.write_str("\x1b[2m"));
} else {
try!(f.write_str("\x1b[22m"));
}
}
if self.underline.is_some() {
if self.underline.unwrap() {
try!(f.write_str("\x1b[4m"));
} else {
try!(f.write_str("\x1b[24m"));
}
}
if self.blink.is_some() {
if self.blink.unwrap() {
try!(f.write_str("\x1b[5m"));
} else {
try!(f.write_str("\x1b[25m"));
}
}
if self.reverse.is_some() {
if self.reverse.unwrap() {
try!(f.write_str("\x1b[7m"));
} else {
try!(f.write_str("\x1b[27m"));
}
}
try!(f.write_str(&self.text));
try!(f.write_str("\x1b[0m"));
Ok(())
}
}
fn build_prompt_text(text: &str, suffix: &str, show_default: bool,
default: Option<&str>) -> String {
let prompt_text: String;
if default.is_some() && show_default {
prompt_text = format!("{} [{}]", text, default.unwrap());
} else {
prompt_text = text.to_string();
}
prompt_text + suffix
}
fn get_prompt_input(prompt_text: &str, hide_input: bool) -> String {
print!("{}", prompt_text);
let mut input = String::new();
io::stdin().read_line(&mut input).ok().expect("Failed to read line");
return input.trim_right_matches("\n").to_string();
}
pub fn prompt(text: &str, default: Option<&str>, hide_input: bool, confirmation: bool,
prompt_suffix: &str, show_default: bool) -> String {
let prompt_text = build_prompt_text(text, prompt_suffix, show_default, default.clone());
let mut prompt_input: String;
loop {
prompt_input = get_prompt_input(&prompt_text, hide_input);
if prompt_input != String::new() {
break
} else if default.is_some() {
return default.unwrap().to_string();
}
}
if !confirmation {
return prompt_input;
}
let mut confirm_input: String;
loop {
confirm_input = get_prompt_input("Repeat for confirmation: ", hide_input);
if confirm_input != String::new() {
break
}
}
if prompt_input == confirm_input {
return prompt_input;
} else {
panic!("Error: the two entered values do not match");
}
}
pub fn confirm(text: &str, default: bool, prompt_suffix: &str, show_default: bool) -> bool {
let default_string = match default {
true => Some("Y/n"),
false => Some("y/N"),
};
let prompt_text = build_prompt_text(text, prompt_suffix, show_default, default_string);
loop {
let prompt_input = get_prompt_input(&prompt_text, false).to_ascii_lowercase();
match prompt_input.trim() {
"y" | "yes" => { return true; },
"n" | "no" => { return false; },
"" => { return default; },
_ => { println!("Error: invalid input"); },
}
}
}
#[repr(C)]
struct WinSize {
ws_row: libc::c_ushort, ws_col: libc::c_ushort, ws_xpixel: libc::c_ushort, ws_ypixel: libc::c_ushort, }
const TIOCGWINSZ: libc::c_ulong = 0x40087468;
pub fn get_terminal_size() -> io::Result<(isize, isize)> {
let w = WinSize {
ws_row: 0,
ws_col: 0,
ws_xpixel: 0,
ws_ypixel: 0
};
let r = unsafe { ioctl(libc::STDOUT_FILENO, TIOCGWINSZ, &w) };
match r {
0 => Ok((w.ws_col as isize, w.ws_row as isize)),
code => Err(io::Error::from_raw_os_error(code)),
}
}
pub fn print_via_pager(text: &str) {
let mut pager = process::Command::new("less").stdin(process::Stdio::piped())
.spawn()
.unwrap_or_else(|e| { panic!("failed to spawn less: {}", e) });
pager.stdin.as_mut().unwrap().write_all(text.as_bytes())
.unwrap_or_else(|e| { panic!("failed to write to less: {}", e) });
pager.wait().unwrap();
}
pub fn isatty() -> bool {
let isatty = unsafe { libc::isatty(libc::STDOUT_FILENO) };
isatty != 0
}
pub fn clear() {
io::stdout().write_all("\x1b[2J\x1b[1;1H".as_bytes()).unwrap()
}
const BEFORE_BAR: &'static str = "\r\x1b[?25l";
const AFTER_BAR: &'static str = "\x1b[?25h\n";
pub struct ProgressBar<'a> {
pub length: isize, pub label: &'a str, pub fill_char: char, pub empty_char: char, pub width: isize, started: bool,
finished: bool,
pos: isize,
start: Instant,
is_hidden: bool,
avgs: Vec<f32>,
last_line_width: usize,
}
impl<'a> ProgressBar<'a> {
pub fn new(length: isize, label: &'a str) -> ProgressBar {
ProgressBar {
length: length,
label: label,
fill_char: '#',
empty_char: ' ',
width: 30,
started: false,
finished: false,
pos: 0,
start: Instant::now(),
is_hidden: !isatty(),
avgs: Vec::with_capacity(11),
last_line_width: 0,
}
}
pub fn begin(&mut self) {
self.started = true;
self.start = Instant::now();
self.render_progress();
}
pub fn end(&mut self) {
self.finished = true;
self.render_progress();
self.render_finish();
}
fn render_finish(&self) {
if self.is_hidden {
return
}
io::stdout().write_all(AFTER_BAR.as_bytes()).unwrap()
}
fn percent(&self) -> f32 {
if self.finished {
return 1.0
}
if self.pos <= self.length {
return self.pos as f32 / self.length as f32;
} else {
return 1.0
}
}
fn time_per_iteration(&self) -> f32 {
if self.avgs.len() == 0 {
return 0.0;
}
let avg_sum = self.avgs.iter().fold(0f32, |x, &y| x + y);
return avg_sum / (self.avgs.len() as f32);
}
fn estimate_time(&self) -> f32 {
if self.finished {
return 0.0
}
let remaining_step = (self.length - self.pos) as f32;
self.time_per_iteration() * remaining_step
}
fn format_percent(&self) -> String {
format!("{:>3}%", (self.percent() * 100f32) as isize)
}
fn format_estimate_time(&self) -> String {
let tm = match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(dur) => dur,
Err(err) => err.duration(),
};
let secs = tm.as_secs();
format!("{:02}:{:02}:{:02}", secs / 3600, (secs / 60) % 60, secs % 60)
}
fn format_progress_line(&self) -> String {
let mut bar_str = String::with_capacity(self.width as usize);
let fill_length = (self.percent() * self.width as f32) as isize;
let empty_length = self.width - fill_length;
for _ in 0..fill_length {
bar_str.push(self.fill_char);
}
for _ in 0..empty_length {
bar_str.push(self.empty_char);
}
let mut info: String;
if self.finished || self.start.elapsed().as_secs() == 0 {
info = format!("{}", self.format_percent());
} else {
info = format!("{} {}", self.format_percent(), self.format_estimate_time());
}
format!("{} [{}] {}", self.label, bar_str, info)
}
fn render_progress(&mut self) {
if self.is_hidden {
return
}
io::stdout().write_all(BEFORE_BAR.as_bytes()).unwrap();
let last_line_width = self.last_line_width;
let line = self.format_progress_line();
let line_width = line.len();
self.last_line_width = line_width;
io::stdout().write_all(line.as_bytes()).unwrap();
if last_line_width > line_width {
let mut clear_string = "".to_string();
for _ in 0..last_line_width - line_width {
clear_string = clear_string + " ";
}
io::stdout().write_all(clear_string.as_bytes()).unwrap();
}
}
pub fn next(&mut self) {
if self.is_hidden {
return
}
self.pos = self.pos + 1;
if self.pos >= self.length {
self.finished = true;
}
let avg: f32 = self.start.elapsed().as_secs() as f32 / self.pos as f32;
self.avgs.insert(0, avg);
self.avgs.truncate(10);
self.render_progress();
}
}
pub struct Editor<'a, 'k, 'v> {
editor: &'a str,
env_map: HashMap<&'k str, &'v str>,
}
impl<'a, 'k, 'v> Editor<'a, 'k, 'v> {
pub fn new(editor: &'a str) -> Editor {
Editor {
editor: editor,
env_map: HashMap::new(),
}
}
pub fn env(&mut self, key: &'k str, value: &'v str) {
self.env_map.insert(key, value);
}
pub fn edit_file(&self, filename: &str) {
let mut edit = process::Command::new(self.editor);
edit.arg(filename);
for (k, v) in self.env_map.iter() {
edit.env(k, v);
}
let status = edit.status().unwrap_or_else(|e| {
panic!("Editing failed: {}", e)
});
if !status.success() {
panic!("Editing failed!")
}
}
pub fn edit(&self, text: String, extension: &str) -> String {
let tmpdir = TempDir::new("clt").unwrap();
let tmpname = "clt_editor".to_string() + extension;
let mut filepath = tmpdir.path().clone();
filepath.join(&tmpname);
let filename = filepath.to_str().unwrap();
let mut tmpfile = File::create(filename).unwrap();
tmpfile.write(text.as_bytes()).unwrap();
tmpfile.flush().unwrap();
self.edit_file(filename);
let mut edited_file = File::open(filename).unwrap();
let mut edited_text = String::new();
edited_file.read_to_string(&mut edited_text).unwrap();
return edited_text;
}
}