rust_cli 0.1.0

A mixin for creating command line applications - gives an easy interface for argument specification and processing used by https://www.megam.io https://www.megam.io. An implementation using rust_cli can be found at https://github.com/megamsys/meg.git
use std::fmt;
use std::io::prelude::*;
use std::io;

use term::Attr;
use term::color::{Color, BLACK, RED, GREEN, YELLOW};
use term::{Terminal, TerminfoTerminal, color};

use self::AdequateTerminal::{NoColor, Colored};

#[derive(Copy, Clone)]
pub struct ShellConfig {
    pub color: bool,
    pub verbose: bool,
    pub tty: bool
}

enum AdequateTerminal {
    NoColor(Box<Write + Send>),
    Colored(Box<Terminal<UghWhyIsThisNecessary> + Send>)
}

pub struct Shell {
    terminal: AdequateTerminal,
    config: ShellConfig,
}

pub struct MultiShell {
    out: Shell,
    err: Shell,
    verbose: bool
}

struct UghWhyIsThisNecessary {
    inner: Box<Write + Send>,
}

impl MultiShell {
    pub fn new(out: Shell, err: Shell, verbose: bool) -> MultiShell {
        MultiShell { out: out, err: err, verbose: verbose }
    }

    pub fn out(&mut self) -> &mut Shell {
        &mut self.out
    }

    pub fn err(&mut self) -> &mut Shell {
        &mut self.err
    }

    pub fn say<T: ToString>(&mut self, message: T, color: Color) -> io::Result<()> {
        self.out().say(message, color)
    }

    pub fn status<T, U>(&mut self, status: T, message: U) -> io::Result<()>
        where T: fmt::Display, U: fmt::Display
    {
        self.out().say_status(status, message, GREEN)
    }

    pub fn verbose<F>(&mut self, mut callback: F) -> io::Result<()>
        where F: FnMut(&mut MultiShell) -> io::Result<()>
    {
        if self.verbose { return callback(self) }
        Ok(())
    }

    pub fn concise<F>(&mut self, mut callback: F) -> io::Result<()>
        where F: FnMut(&mut MultiShell) -> io::Result<()>
    {
        if !self.verbose { return callback(self) }
        Ok(())
    }

    pub fn error<T: ToString>(&mut self, message: T) -> io::Result<()> {
        self.err().say(message, RED)
    }

    pub fn warn<T: ToString>(&mut self, message: T) -> io::Result<()> {
        self.err().say(message, YELLOW)
    }

    pub fn set_verbose(&mut self, verbose: bool) {
        self.verbose = verbose;
    }

    pub fn get_verbose(&self) -> bool {
        self.verbose
    }
}

impl Shell {
    pub fn create(out: Box<Write + Send>, config: ShellConfig) -> Shell {
        let out = UghWhyIsThisNecessary { inner: out };
        if config.tty && config.color {
            let term = TerminfoTerminal::new(out);
            term.map(|t| Shell {
                terminal: Colored(Box::new(t)),
                config: config
            }).unwrap_or_else(|| {
                Shell { terminal: NoColor(Box::new(io::stderr())), config: config }
            })
        } else {
            Shell { terminal: NoColor(out.inner), config: config }
        }
    }

    pub fn verbose<F>(&mut self, mut callback: F) -> io::Result<()>
        where F: FnMut(&mut Shell) -> io::Result<()>
    {
        if self.config.verbose { return callback(self) }
        Ok(())
    }

    pub fn concise<F>(&mut self, mut callback: F) -> io::Result<()>
        where F: FnMut(&mut Shell) -> io::Result<()>
    {
        if !self.config.verbose { return callback(self) }
        Ok(())
    }

    pub fn say<T: ToString>(&mut self, message: T, color: Color) -> io::Result<()> {
        try!(self.reset());
        if color != BLACK { try!(self.fg(color)); }
        try!(write!(self, "{}\n", message.to_string()));
        try!(self.reset());
        try!(self.flush());
        Ok(())
    }

    pub fn say_status<T, U>(&mut self, status: T, message: U, color: Color)
                            -> io::Result<()>
        where T: fmt::Display, U: fmt::Display
    {
        try!(self.reset());
        if color != BLACK { try!(self.fg(color)); }
        if self.supports_attr(Attr::Bold) { try!(self.attr(Attr::Bold)); }
        try!(write!(self, "{:>12}", status.to_string()));
        try!(self.reset());
        try!(write!(self, " {}\n", message));
        try!(self.flush());
        Ok(())
    }

    fn fg(&mut self, color: color::Color) -> io::Result<bool> {
        match self.terminal {
            Colored(ref mut c) => c.fg(color),
            NoColor(_) => Ok(false)
        }
    }

    fn attr(&mut self, attr: Attr) -> io::Result<bool> {
        match self.terminal {
            Colored(ref mut c) => c.attr(attr),
            NoColor(_) => Ok(false)
        }
    }

    fn supports_attr(&self, attr: Attr) -> bool {
        match self.terminal {
            Colored(ref c) => c.supports_attr(attr),
            NoColor(_) => false
        }
    }

    fn reset(&mut self) -> io::Result<()> {
        match self.terminal {
            Colored(ref mut c) => c.reset().map(|_| ()),
            NoColor(_) => Ok(())
        }
    }
}

impl Write for Shell {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        match self.terminal {
            Colored(ref mut c) => c.write(buf),
            NoColor(ref mut n) => n.write(buf)
        }
    }

    fn flush(&mut self) -> io::Result<()> {
        match self.terminal {
            Colored(ref mut c) => c.flush(),
            NoColor(ref mut n) => n.flush()
        }
    }
}

impl Write for UghWhyIsThisNecessary {
    fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
        self.inner.write(bytes)
    }
    fn flush(&mut self) -> io::Result<()> {
        self.inner.flush()
    }
}