ttyaskpass 2.0.0-alpha

a safely passphrase prompt library.
Documentation
extern crate libc;
extern crate rand;
extern crate seckey;
extern crate termion;
extern crate sha3;

pub mod readtty;
pub mod colorhash;

use std::io::{ self, Read, Write };
use std::iter::repeat;
use rand::random;
use seckey::SecKey;
use termion::clear;
use termion::get_tty;
use termion::event::Key;
use termion::color::{ Fg, Reset, AnsiValue };
use colorhash::{ hash_as_ansi, hash_chars_as_ansi };
use readtty::{ RawTTY, read_from_tty };


/// Askpass
///
/// ### Fail When:
///
/// - IO Error
/// - User Interrupted
/// - `RawTTY` create fail
/// - `SecKey` malloc fail
#[inline]
pub fn askpass<F>(prompt: &str, f: F)
    -> io::Result<()>
    where F: FnOnce(&[char]) -> io::Result<()>
{
    raw_askpass(&mut RawTTY::new()?, &mut get_tty()?, prompt, '*')
        .and_then(|(buf, pos)| f(&buf.read()[..pos]))
}

pub fn raw_askpass(input: &mut Read, output: &mut Write, prompt: &str, star: char)
    -> io::Result<(SecKey<[char; 256]>, usize)>
{
    let mut pos = 0;
    let mut buf = SecKey::new([char::default(); 256])
        .map_err(|_| io::Error::new(io::ErrorKind::Other, "SecKey malloc fail"))?;

    read_from_tty(input, |key| {
        let mut buf = buf.write();
        match key {
            Key::Char('\n') => return Ok(true),
            Key::Char(c) => if pos < buf.len() {
                buf[pos] = c;
                pos += 1;
            },
            Key::Backspace | Key::Delete if pos >= 1 => pos -= 1,
            Key::Ctrl('c') => return Err(io::Error::new(io::ErrorKind::Interrupted, "Ctrl-c")),
            Key::Null => (),
            _ => return Ok(false)
        }

        let colors = match pos {
            0 => [AnsiValue(30); 8],
            1...7 => hash_as_ansi(&[random(), random(), random()]),
            p => hash_chars_as_ansi(&buf[..p])
        };

        write!(
            output,
            "\r{} {}{}",
            prompt,
            repeat(star)
                .take(4)
                .zip(&colors[..4])
                .map(|(star, &color)| format!("{}{}{}", Fg(color), star, star))
                .collect::<String>(),
            Fg(Reset)
        )?;

        Ok(false)
    })?;

    write!(output, "{}\r", clear::CurrentLine)?;

    Ok((buf, pos))
}