use libc::isatty;
use std::fmt;
use std::io;
use std::io::prelude::*;
use term::{self, Attr, TerminfoTerminal};
use term::Terminal as RawTerminal;
use term::color::{Color, BLACK};
#[derive(Clone, Copy, PartialEq)]
pub enum ColorConfig {
Auto,
Always,
Never,
}
impl fmt::Display for ColorConfig {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ColorConfig::Auto => "auto",
ColorConfig::Always => "always",
ColorConfig::Never => "never",
}.fmt(f)
}
}
#[derive(Clone, Copy)]
pub struct ShellConfig {
pub color_config: ColorConfig,
pub tty: bool,
}
impl Default for ShellConfig {
fn default() -> Self {
Self {
color_config: ColorConfig::Auto,
tty: is_tty(),
}
}
}
fn is_tty() -> bool {
#[allow(unsafe_code)]
unsafe {
isatty(0) == 1
}
}
enum Terminal {
NoColor(Box<Write + Send>),
Colored(Box<RawTerminal<Output = Box<Write + Send>> + Send>),
}
pub struct Shell {
terminal: Terminal,
config: ShellConfig,
}
impl Shell {
pub fn create<T: FnMut() -> Box<Write + Send>>(mut out_fn: T, config: ShellConfig) -> Shell {
let terminal = Shell::get_term(out_fn()).unwrap_or_else(|_| Terminal::NoColor(out_fn()));
Shell { terminal, config }
}
fn get_term(out: Box<Write + Send>) -> term::Result<Terminal> {
Ok(Shell::get_terminfo_term(out))
}
fn get_terminfo_term(out: Box<Write + Send>) -> Terminal {
match ::term::terminfo::TermInfo::from_env() {
Ok(ti) => {
let term = TerminfoTerminal::new_with_terminfo(out, ti);
if term.supports_color() {
Terminal::Colored(Box::new(term))
} else {
Terminal::NoColor(term.into_inner())
}
}
Err(_) => Terminal::NoColor(out),
}
}
pub fn status<T, U>(
&mut self,
color: Color,
status: T,
message: U,
justified: bool,
) -> term::Result<()>
where
T: fmt::Display,
U: fmt::Display,
{
self.reset()?;
if color != BLACK {
self.fg(color)?;
}
if self.supports_attr(Attr::Bold) {
self.attr(Attr::Bold)?;
}
if justified {
write!(self, "{:>12}", status.to_string())?;
} else {
write!(self, "{}", status)?;
}
self.reset()?;
write!(self, " {}\n", message)?;
self.flush()?;
Ok(())
}
fn fg(&mut self, color: Color) -> term::Result<bool> {
let colored = self.colored();
match self.terminal {
Terminal::Colored(ref mut c) if colored => c.fg(color)?,
_ => return Ok(false),
}
Ok(true)
}
fn attr(&mut self, attr: Attr) -> term::Result<bool> {
let colored = self.colored();
match self.terminal {
Terminal::Colored(ref mut c) if colored => c.attr(attr)?,
_ => return Ok(false),
}
Ok(true)
}
fn supports_attr(&self, attr: Attr) -> bool {
let colored = self.colored();
match self.terminal {
Terminal::Colored(ref c) if colored => c.supports_attr(attr),
_ => false,
}
}
fn reset(&mut self) -> term::Result<()> {
let colored = self.colored();
match self.terminal {
Terminal::Colored(ref mut c) if colored => c.reset()?,
_ => (),
}
Ok(())
}
fn colored(&self) -> bool {
self.config.tty && ColorConfig::Auto == self.config.color_config
|| ColorConfig::Always == self.config.color_config
}
}
impl Default for Shell {
fn default() -> Self {
Shell::create(|| Box::new(io::stdout()), ShellConfig::default())
}
}
impl Write for Shell {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self.terminal {
Terminal::Colored(ref mut c) => c.write(buf),
Terminal::NoColor(ref mut n) => n.write(buf),
}
}
fn flush(&mut self) -> io::Result<()> {
match self.terminal {
Terminal::Colored(ref mut c) => c.flush(),
Terminal::NoColor(ref mut n) => n.flush(),
}
}
}