<commit_before>extern crate mio;
extern crate bytes;
extern crate log4rs;
#[macro_use]
extern crate log;
#[macro_use]
extern crate clap;

use mio::tcp::*;
use mio::{Sender,EventLoop};
use mio::{TryRead,TryWrite};
use mio::util::Slab;
use std::thread;
use std::sync::mpsc::{self,channel};
use std::io::Cursor;
use clap::{Arg, App, SubCommand};

use bytes::{Buf, Take};

const SERVER: mio::Token = mio::Token(0);

#[derive(Debug)]
enum MiChatCommand {
    Set(String),
    Get(mio::Token),
}

#[derive(Debug)]
struct MiChatIoEvent (mio::Token, String);

struct MiChat {
    listener: TcpListener,
    connections: Slab<Connection>,
    commands: mpsc::Sender<MiChatCommand>
}

impl MiChat {
    fn new(listener: TcpListener, commands: mpsc::Sender<MiChatCommand>) -> MiChat {
        MiChat {
            listener: listener,
            connections: Slab::new_starting_at(mio::Token(1), 1024),
            commands: commands
        }
    }
}

struct Connection {
    socket: TcpStream,
    token: mio::Token,
    state: Option<Vec<u8>>,
    write_buf: Vec<u8>,
    commands: mpsc::Sender<MiChatCommand>,
}

impl Connection {
    fn new(socket: TcpStream, token: mio::Token, commands: mpsc::Sender<MiChatCommand>) -> Connection {
        Connection {
            socket: socket,
            token: token,
            state: Some(Vec::with_capacity(1024)),
            write_buf: Vec::new(),
            commands: commands,
        }
    }
    fn ready(&mut self, event_loop: &mut mio::EventLoop<MiChat>, events: mio::EventSet) {
        info!("Connection::ready: {:?}; {:?}", self.socket.peer_addr(), events);
        if events.is_readable() {
            self.read(event_loop)
        }
        if events.is_writable() {
            self.write(event_loop)
        }
    }

    fn read(&mut self, event_loop: &mut mio::EventLoop<MiChat>) {
        let mut abuf = Vec::new();
        match self.socket.try_read_buf(&mut abuf) {
            Ok(Some(0)) => {
                info!("{:?}: EOF!", self.socket.peer_addr());
                self.finish()
            },
            Ok(Some(n)) => {
                info!("{:?}: Read {}bytes", self.socket.peer_addr(), n);
                self.ingest(abuf);
                self.reregister(event_loop)
            },
            Ok(None) => {
                info!("{:?}: Noop!", self.socket.peer_addr());
                self.reregister(event_loop)
            },
            Err(e) => panic!("got an error trying to read; err={:?}", e)
        }
    }

    fn notify(&mut self, event_loop: &mut mio::EventLoop<MiChat>, s: String) {
        self.write_buf.extend(s.bytes());
        self.write_buf.push('\n' as u8);
        self.write(event_loop);
        self.reregister(event_loop);
    }

    fn write(&mut self, event_loop: &mut mio::EventLoop<MiChat>) {
        match self.socket.try_write(&mut self.write_buf) {
            Ok(Some(n)) => {
                info!("Wrote {} of {} in buffer", n, self.write_buf.len());
                self.write_buf = self.write_buf[n..].to_vec();
                self.reregister(event_loop);
            },
            Ok(None) => {
                info!("Write unready");
                self.reregister(event_loop);
            },
            Err(e) => {
                panic!("got an error trying to write; err={:?}", e);
            }
        }
    }

    fn reregister(&mut self, event_loop: &mut mio::EventLoop<MiChat>) {
        let mut flags = mio::EventSet::readable();
        if !self.write_buf.is_empty() {
            flags = flags | mio::EventSet::writable();
        }

        event_loop.reregister(
                &self.socket,
                self.token,
                flags,
                mio::PollOpt::oneshot()).unwrap()

    }
    fn ingest(&mut self, inbuf: Vec<u8>) {
        match self.state {
            Some(ref mut buf)  => {
                let mut startpos = 0;
                while let Some(n) = inbuf.iter().skip(startpos).position(|e| *e == '\n' as u8) {
                    Self::buffer_slice(buf, &inbuf[startpos..startpos+n]);
                    let s = String::from_utf8_lossy(buf).into_owned();
                    let cmd = if s.is_empty() { MiChatCommand::Get(self.token) } else { MiChatCommand::Set(s) };
                    self.commands.send(cmd).unwrap();
                    startpos += n+1;
                    buf.clear()
                }
                info!("Remainder: {}", String::from_utf8_lossy(&inbuf[startpos..]));
                Self::buffer_slice(buf, &inbuf[startpos..])
            },
            None => panic!("Ingest on closed connection?")
        }
    }

    fn finish(&mut self) {
        match self.state {
            Some(ref mut buf)  => info!("Remainder: {}", String::from_utf8_lossy(buf)),
            _ => ()
        }
        self.state = None

    }

    fn buffer_slice(buf: &mut Vec<u8>, slice: &[u8]) {
        buf.extend(slice.iter().cloned());
    }

    fn is_closed(&self) -> bool {
        self.state.is_none() && self.write_buf.is_empty()
    }
}

impl mio::Handler for MiChat {
    type Timeout = ();
    type Message = MiChatIoEvent;
    fn ready(&mut self, event_loop: &mut mio::EventLoop<Self>, token: mio::Token, events: mio::EventSet) {
        info!("{:?}: {:?}", token, events);
        match token {
            SERVER => {
                // Only receive readable events
                assert!(events.is_readable());

                info!("the listener socket is ready to accept a connection");
                match self.listener.accept() {
                    Ok(Some(socket)) => {
                        let commands = self.commands.clone();
                        let token = self.connections
                            .insert_with(|token| Connection::new(socket, token, commands))
                            .unwrap();

                        event_loop.register_opt(
                            &self.connections[token].socket,
                            token,
                            mio::EventSet::readable(),
                            mio::PollOpt::edge() | mio::PollOpt::oneshot()).unwrap();
                    }
                    Ok(None) => {
                        info!("the listener socket wasn't actually ready");
                    }
                    Err(e) => {
                        info!("listener.accept() errored: {}", e);
                        event_loop.shutdown();
                    }
                }
            }
            _ => {
                self.connections[token].ready(event_loop, events);
                if self.connections[token].is_closed() {
                    self.connections.remove(token);
                }
            }
        }
    }

    fn notify(&mut self, event_loop: &mut EventLoop<Self>, msg: MiChatIoEvent) {
        info!("Notify: {:?}", msg);
        let MiChatIoEvent(token, s) = msg;
        if let Some(conn) = self.connections.get_mut(token) {
            conn.notify(event_loop, s);
        } else {
            warn!("No connection for token: {:?}", token);
        }
    }
}

#[derive(Debug)]
struct Model {
    value: String
}

impl Model {
    fn new() -> Model {
        Model { value: "".to_string() }
    }

    fn process_from(&mut self, rx: mpsc::Receiver<MiChatCommand>, replies: Sender<MiChatIoEvent>) {
        loop {
            let msg = rx.recv().unwrap();
            info!("{:?}; got {:?}", self, msg);
            match msg {
                MiChatCommand::Set(s) => {
                    self.value = s;
                },
                MiChatCommand::Get(tok) => {
                    replies.send(MiChatIoEvent(tok, self.value.clone())).unwrap();
                }
            }
        }
    }
}

const LOG_FILE: &'static str = "log.toml";

fn main() {
    if let Err(e) = log4rs::init_file(LOG_FILE, Default::default()) {
        panic!("Could not init logger from file {}: {}", LOG_FILE, e);
    }
    let matches = App::new("michat")
        .arg(Arg::with_name("bind").short("l").takes_value(true).required(true))
        .get_matches();

    let address = value_t_or_exit!(matches.value_of("bind"), std::net::SocketAddr);
    let listener = TcpListener::bind(&address).unwrap();

    let mut event_loop = mio::EventLoop::new().unwrap();
    event_loop.register(&listener, SERVER).unwrap();

    info!("running michat listener at: {:?}", address);

    let (command_tx, command_rx) = channel();
    let replies = event_loop.channel();
    let t = thread::Builder::new().name("Model".to_owned()).spawn(move|| {
        let mut model = Model::new();
        model.process_from(command_rx, replies);
    }).unwrap();

    let mut service = MiChat::new(listener, command_tx);
    event_loop.run(&mut service).unwrap();
    t.join().unwrap();
}
<commit_msg>Refactor to seperate event handling, logic, and actions.<commit_after>extern crate mio;
extern crate bytes;
extern crate log4rs;
#[macro_use]
extern crate log;
#[macro_use]
extern crate clap;

use mio::tcp::*;
use mio::{Sender,EventLoop};
use mio::{TryRead,TryWrite};
use mio::util::Slab;
use std::thread;
use std::sync::mpsc::{self,channel};
use std::io::Cursor;
use clap::{Arg, App, SubCommand};

use bytes::{Buf, Take};

const SERVER: mio::Token = mio::Token(0);

#[derive(Debug)]
enum MiChatCommand {
    Set(String),
    Get(mio::Token),
}

#[derive(Debug)]
struct MiChatIoEvent (mio::Token, String);

struct MiChat {
    listener: TcpListener,
    connections: Slab<Connection>,
    commands: mpsc::Sender<MiChatCommand>
}

impl MiChat {
    fn new(listener: TcpListener, commands: mpsc::Sender<MiChatCommand>) -> MiChat {
        MiChat {
            listener: listener,
            connections: Slab::new_starting_at(mio::Token(1), 1024),
            commands: commands
        }
    }
}

struct Connection {
    socket: TcpStream,
    sock_status: mio::EventSet,
    token: mio::Token,
    read_buf: Vec<u8>,
    read_eof: bool,
    failed: bool,
    write_buf: Vec<u8>,
    commands: mpsc::Sender<MiChatCommand>,
}

impl Connection {
    fn new(socket: TcpStream, token: mio::Token, commands: mpsc::Sender<MiChatCommand>) -> Connection {
        Connection {
            socket: socket,
            sock_status: mio::EventSet::none(),
            token: token,
            read_buf: Vec::with_capacity(1024),
            write_buf: Vec::new(),
            read_eof: false,
            failed: false,
            commands: commands,
        }
    }
    fn ready(&mut self, event_loop: &mut mio::EventLoop<MiChat>, events: mio::EventSet) {
        self.sock_status.insert(events);
        info!("Connection::ready: {:?}; this time: {:?}; now: {:?}",
                self.socket.peer_addr(), events, self.sock_status);
        if self.sock_status.is_readable() {
            self.read(event_loop);
            self.sock_status.remove(mio::EventSet::readable());
        }

        self.process();

        if self.sock_status.is_writable() {
            self.write(event_loop);
            self.sock_status.remove(mio::EventSet::writable());
        }

        if !self.is_closed() {
            self.reregister(event_loop)
        }
    }

    fn read(&mut self, event_loop: &mut mio::EventLoop<MiChat>) {
        let mut abuf = Vec::new();
        match self.socket.try_read_buf(&mut abuf) {
            Ok(Some(0)) => {
                info!("{:?}: EOF!", self.socket.peer_addr());
                self.read_eof = true
            },
            Ok(Some(n)) => {
                info!("{:?}: Read {}bytes", self.socket.peer_addr(), n);
                self.read_buf.extend(abuf);
            },
            Ok(None) => {
                info!("{:?}: Noop!", self.socket.peer_addr());
            },
            Err(e) => {
                error!("got an error trying to read; err={:?}", e);
                self.failed =true;
            }
        }
    }

    fn notify(&mut self, event_loop: &mut mio::EventLoop<MiChat>, s: String) {
        self.write_buf.extend(s.bytes());
        self.write_buf.push('\n' as u8);
        self.write(event_loop);
        self.reregister(event_loop);
    }

    fn write(&mut self, event_loop: &mut mio::EventLoop<MiChat>) {
        match self.socket.try_write(&mut self.write_buf) {
            Ok(Some(n)) => {
                info!("Wrote {} of {} in buffer", n, self.write_buf.len());
                self.write_buf = self.write_buf[n..].to_vec();
            },
            Ok(None) => {
                info!("Write unready");
            },
            Err(e) => {
                error!("got an error trying to write; err={:?}", e);
                self.failed = true;
            }
        }
    }

    fn reregister(&mut self, event_loop: &mut mio::EventLoop<MiChat>) {
        let mut flags = mio::EventSet::readable();
        if !self.write_buf.is_empty() {
            flags.insert(mio::EventSet::writable());
        }

        event_loop.reregister(
                &self.socket,
                self.token,
                flags,
                mio::PollOpt::oneshot()).expect("EventLoop#reregister")

    }
    fn process(&mut self) {
        let mut prev = 0;
        for n in self.read_buf.iter().enumerate()
                .filter_map(|(i, e)| if *e == '\n' as u8 { Some(i) } else { None } ) {
            let s = String::from_utf8_lossy(&self.read_buf[prev..n]).into_owned();
            let cmd = if s.is_empty() { MiChatCommand::Get(self.token) } else { MiChatCommand::Set(s) };
            info!("Send! {:?}", cmd);
            self.commands.send(cmd).expect("Sender#send");
            prev = n;
        }
        let remainder = self.read_buf[prev..].to_vec();
        info!("Remainder: {}", String::from_utf8_lossy(&remainder));
        self.read_buf = remainder;
    }

    fn buffer_slice(buf: &mut Vec<u8>, slice: &[u8]) {
        buf.extend(slice.iter().cloned());
    }

    fn is_closed(&self) -> bool {
        self.failed || (self.read_eof && self.write_buf.is_empty())
    }
}

impl mio::Handler for MiChat {
    type Timeout = ();
    type Message = MiChatIoEvent;
    fn ready(&mut self, event_loop: &mut mio::EventLoop<Self>, token: mio::Token, events: mio::EventSet) {
        info!("{:?}: {:?}", token, events);
        match token {
            SERVER => {
                // Only receive readable events
                assert!(events.is_readable());

                info!("the listener socket is ready to accept a connection");
                match self.listener.accept() {
                    Ok(Some(socket)) => {
                        let commands = self.commands.clone();
                        let token = self.connections
                            .insert_with(|token| Connection::new(socket, token, commands))
                            .expect("token insert");

                        event_loop.register_opt(
                            &self.connections[token].socket,
                            token,
                            mio::EventSet::readable(),
                            mio::PollOpt::edge() | mio::PollOpt::oneshot())
                        .expect("event loop register");
                    }
                    Ok(None) => {
                        info!("the listener socket wasn't actually ready");
                    }
                    Err(e) => {
                        info!("listener.accept() errored: {}", e);
                        event_loop.shutdown();
                    }
                }
            }
            _ => {
                self.connections[token].ready(event_loop, events);
                if self.connections[token].is_closed() {
                    info!("Removing; {:?}", token);
                    self.connections.remove(token);
                }
            }
        }
    }

    fn notify(&mut self, event_loop: &mut EventLoop<Self>, msg: MiChatIoEvent) {
        info!("Notify: {:?}", msg);
        let MiChatIoEvent(token, s) = msg;
        if let Some(conn) = self.connections.get_mut(token) {
            conn.notify(event_loop, s);
        } else {
            warn!("No connection for token: {:?}", token);
        }
    }
}

#[derive(Debug)]
struct Model {
    value: String
}

impl Model {
    fn new() -> Model {
        Model { value: "".to_string() }
    }

    fn process_from(&mut self, rx: mpsc::Receiver<MiChatCommand>, replies: Sender<MiChatIoEvent>) {
        loop {
            let msg = rx.recv().expect("Model receive");
            info!("{:?}; got {:?}", self, msg);
            match msg {
                MiChatCommand::Set(s) => {
                    self.value = s;
                },
                MiChatCommand::Get(tok) => {
                    while let Err(e) = replies.send(MiChatIoEvent(tok, self.value.clone())) {
                        info!("Backoff on notify: {:?}", e);
                        thread::sleep_ms(10);
                    }
                }
            }
        }
    }
}

const LOG_FILE: &'static str = "log.toml";

fn main() {
    if let Err(e) = log4rs::init_file(LOG_FILE, Default::default()) {
        panic!("Could not init logger from file {}: {}", LOG_FILE, e);
    }
    let matches = App::new("michat")
        .arg(Arg::with_name("bind").short("l").takes_value(true).required(true))
        .get_matches();

    let address = value_t_or_exit!(matches.value_of("bind"), std::net::SocketAddr);
    let listener = TcpListener::bind(&address).expect("bind");

    let mut event_loop = mio::EventLoop::new().expect("Create event loop");
    event_loop.register(&listener, SERVER).expect("Register listener");

    info!("running michat listener at: {:?}", address);

    let (command_tx, command_rx) = channel();
    let replies = event_loop.channel();
    let t = thread::Builder::new().name("Model".to_owned()).spawn(move|| {
        let mut model = Model::new();
        model.process_from(command_rx, replies);
    }).expect("spawn model thread");

    let mut service = MiChat::new(listener, command_tx);
    event_loop.run(&mut service).expect("Run loop");
    t.join().expect("Model join");
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate clap;

extern crate qrcode;
extern crate termion;
extern crate image;
extern crate regex;
extern crate urlparse;

use clap::{App, AppSettings, Arg, Shell, SubCommand};
use qrcode::{QrCode, EcLevel};
use termion::color;
use image::GrayImage;
use std::process::exit;
use std::fs;

mod payloads;

const WIFI_COMMAND: &'static str = "wifi";
const MAIL_COMMAND: &'static str = "mail";
const SMS_COMMAND: &'static str = "sms";
const MMS_COMMAND: &'static str = "mms";
const GEO_COMMAND: &'static str = "geo";
const PHONE_COMMAND: &'static str = "phone";
const SKYPE_COMMAND: &'static str = "skype";
const WHATSAPP_COMMAND: &'static str = "whatsapp";
const URL_COMMAND: &'static str = "url";
const BOOKMARK_COMMAND: &'static str = "bookmark";
// const BITCOIN_COMMAND: &'static str = "bitcoin";
// const GIRO_COMMAND: &'static str = "giro";
// const CALENDAR_COMMAND: &'static str = "calendar";

/*TODO: add arguments for:
- Add help texts for the subcommands
- Add error texts for some exit branches (mostly file output and qrcode gen)
- implement different types of qr payloads
- write tests
*/
fn main() {
    // match all input args
    let matches = build_cli().get_matches();

    // write the completions if they were requested, then exit and dont print any qr-code
    if let Some(comp) = matches.subcommand_matches("completions") {
        let dir = comp.value_of("comp_dir").unwrap();

        let shell = match comp.value_of("shell") {
            Some("ps") => Shell::PowerShell,
            Some("zsh") => Shell::Zsh,
            Some("fish") => Shell::Fish,
            _ => Shell::Bash,
        };

        // create directory if necessary
        fs::create_dir_all(&dir).unwrap();

        let mut app = build_cli();
        app.gen_completions("qr", shell, dir);

        println!("Completion file for the {:?} shell was writen to: {:?}", shell, dir);

        // exit and dont generate a qr-code
        exit(0);
    }

    // deduce the string payload
    let payload = get_payload(&matches);

    // should we draw a white border (safe zone) around the code?
    let safe: bool = match matches.occurrences_of("safe_zone") {
        0 => true,
        _ => false,
    };

    // what error level can we expect? always defaults to "H"
    let error: EcLevel = match matches.value_of("error").unwrap() {
        "L" => EcLevel::L,
        "M" => EcLevel::M,
        "Q" => EcLevel::Q,
        &_ => EcLevel::H,
    };

    //TODO: catch the possible errors
    let code = QrCode::with_error_correction_level(payload, error).unwrap();

    // are we drawing to the terminal or to a file?
    match matches.occurrences_of("output") {
        1 => save(&code, safe, matches.value_of("output").unwrap()),
        _ => draw(&code, safe),
    }
}

// deduces wich kind of string we are going to encode
fn get_payload(matches: &clap::ArgMatches) -> String {
    if let Some(sub) = matches.subcommand_matches(WIFI_COMMAND) {
        let auth = match sub.value_of("mode") {
            Some("WEP") => payloads::Authentication::WEP,
            Some("WPA") => payloads::Authentication::WPA,
            _ => payloads::Authentication::nopass,
        };
        return payloads::wifi_string(sub.value_of("ssid").unwrap(),
                                     sub.value_of("pwd").unwrap(),
                                     &auth,
                                     sub.value_of("hidden").unwrap() == "true");
    } else if let Some(sub) = matches.subcommand_matches(MAIL_COMMAND) {
        let encoding = match sub.value_of("encoding") {
            Some("MATMSG") => payloads::MailEncoding::MATMSG,
            Some("SMTP") => payloads::MailEncoding::SMTP,
            _ => payloads::MailEncoding::MAILTO,
        };
        return payloads::mail_string(sub.value_of("receiver").unwrap(),
                                     sub.value_of("subject").unwrap(),
                                     sub.value_of("message").unwrap(),
                                     &encoding);
    } else if let Some(sub) = matches.subcommand_matches(URL_COMMAND) {
        return payloads::url_string(sub.value_of("url").unwrap());
    } else if let Some(sub) = matches.subcommand_matches(PHONE_COMMAND) {
        return payloads::phone_string(sub.value_of("phone").unwrap());
    } else if let Some(sub) = matches.subcommand_matches(SKYPE_COMMAND) {
        return payloads::skype_string(sub.value_of("name").unwrap());
    } else if let Some(sub) = matches.subcommand_matches(WHATSAPP_COMMAND) {
        return payloads::whatsapp_string(sub.value_of("message").unwrap());
    } else if let Some(sub) = matches.subcommand_matches(SMS_COMMAND) {
        let encoding = match sub.value_of("encoding") {
            Some("SMSTO") => payloads::SMSEncoding::SMSTO,
            Some("SMS_iOS") => payloads::SMSEncoding::SMS_iOS,
            _ => payloads::SMSEncoding::SMS,
        };
        return payloads::sms_string(sub.value_of("number").unwrap(),
                                    sub.value_of("subject").unwrap(),
                                    &encoding);
    } else if let Some(sub) = matches.subcommand_matches(MMS_COMMAND) {
        let encoding = match sub.value_of("encoding") {
            Some("MMSTO") => payloads::MMSEncoding::MMSTO,
            _ => payloads::MMSEncoding::MMS,
        };
        return payloads::mms_string(sub.value_of("number").unwrap(),
                                    sub.value_of("subject").unwrap(),
                                    &encoding);
    } else if let Some(sub) = matches.subcommand_matches(GEO_COMMAND) {
        let encoding = match sub.value_of("encoding") {
            Some("GoogleMaps") => payloads::GeolocationEncoding::GoogleMaps,
            _ => payloads::GeolocationEncoding::GEO,
        };
        return payloads::geo_string(sub.value_of("latitude").unwrap(),
                                    sub.value_of("longitude").unwrap(),
                                    &encoding);
    } else if let Some(sub) = matches.subcommand_matches(BOOKMARK_COMMAND) {
        return payloads::bookmark_string(sub.value_of("title").unwrap(),
                                         sub.value_of("url").unwrap());
    } else {
        return String::from(matches.value_of("INPUT").unwrap());
    }
}

// save to a file at the path
fn save(code: &QrCode, safe: bool, path: &str) {
    // render to a image struct
    let image: GrayImage = code.render().quiet_zone(safe).to_image();

    // save the image
    match image.save(path) {
        Ok(..) => println!("Image successfully saved to: {:?}", path),
        Err(e) => println!("Tried to create file but there was a problem: {:?}",
            if let Some(inner_err) = e.get_ref() { inner_err.to_string() } else { e.to_string() }),
    };
}

// draw to the terminal
fn draw(code: &QrCode, safe: bool) {
    // get "bit" array
    let bit_array = code.to_vec();

    // get the code width and add extra space for the safe zone
    let w = code.width();
    let wide = w + 6;

    // draw the first white safe zone
    if safe {
        for a in 1..(wide * 3) + 1 {
            print!("{}  ", color::Bg(color::LightWhite));
            if a % wide == 0 {
                println!("{}", color::Bg(color::Reset));
            }
        }
    }

    // main drawing loop
    for (i, item) in bit_array.iter().enumerate() {
        // left safe zone
        if safe && i % w == 0 {
            print!("{}      ", color::Bg(color::LightWhite));
        }

        // draw black or white blocks
        if *item {
            print!("{}  ", color::Bg(color::Black));
        } else {
            print!("{}  ", color::Bg(color::LightWhite));
        }

        if (i + 1) % w == 0 {
            if safe {
                //draw right safe zone
                println!("{}      {}",
                         color::Bg(color::LightWhite),
                         color::Bg(color::Reset));
            } else {
                println!("{}", color::Bg(color::Reset));
            }
        }
    }

    //Draw the last white safe zone
    if safe {
        for a in 1..(wide * 3) + 1 {
            print!("{}  ", color::Bg(color::LightWhite));
            if a % wide == 0 {
                println!("{}", color::Bg(color::Reset));
            }
        }
    }
}

// create the interface for the app with all subcommands, flags and args
fn build_cli() -> App<'static, 'static> {
    App::new(crate_name!())
        .version(crate_version!())
        .author(crate_authors!("\n"))
        .about(crate_description!())
        .global_setting(AppSettings::SubcommandsNegateReqs)
        .arg(Arg::with_name("safe_zone")
            .global(true)
            .short("s").long("safe-zone")
            .help("Sets wether the safe zone around the code should be drawn or not.")
            .takes_value(false))
        .arg(Arg::with_name("output")
            .global(true)
            .short("o").long("output")
            .help("Prints the QR-Code to a file. The image format is derived from the file extension. Currently only jpeg and png files are supported.")
            .value_name("FILE"))
        .arg(Arg::with_name("error")
            .global(true)
            .short("e").long("error")
            .help("Set the desired error correction level.")
            .value_name("LEVEL")
            .possible_values(&["L", "M", "Q", "H"])
            .default_value("H"))
        .arg(Arg::with_name("INPUT")
            .help("The input string to use")
            .required(true))
        .subcommand(SubCommand::with_name("completions")
            .about("Outputs completion files for various shells.")
            .arg(Arg::with_name("comp_dir")
                .required(true)
                .help("The directory to write the completion file to.")
                .value_name("DIR"))
            .arg(Arg::with_name("shell")
                .long("shell")
                .help("For which shell the completions should be generated for.")
                .value_name("SHELL")
                .possible_values(&["bash", "zsh", "fish", "ps"])
                .default_value("bash")))
        .subcommand(SubCommand::with_name(WIFI_COMMAND)
            .about("formats to a wifi access string QR-Code")
            .arg(Arg::with_name("ssid").required(true))
            .arg(Arg::with_name("pwd").required(true))
            .arg(Arg::with_name("mode")
                .value_name("MODE")
                .possible_values(&["WEP", "WPA", "nopass"])
                .default_value("WPA"))
            .arg(Arg::with_name("hidden")
                .value_name("HIDDEN")
                .possible_values(&["true", "false"])
                .default_value("false")))
        .subcommand(SubCommand::with_name(MAIL_COMMAND)
            .about("formats to a mail adress string QR-Code")
            .arg(Arg::with_name("receiver").required(true))
            .arg(Arg::with_name("subject"))
            .arg(Arg::with_name("message"))
            .arg(Arg::with_name("encoding")
                .value_name("ENCODING")
                .possible_values(&["MAILTO", "MATMSG", "SMTP"])
                .default_value("MAILTO")))
        .subcommand(SubCommand::with_name(URL_COMMAND)
            .about("formats to an URL QR-Code")
            .arg(Arg::with_name("url")
                .required(true)
                .value_name("URL")))
        .subcommand(SubCommand::with_name(PHONE_COMMAND)
            .about("formats to a phone number QR-Code")
            .arg(Arg::with_name("number")
                .required(true)
                .value_name("NUMBER")))
        .subcommand(SubCommand::with_name(SKYPE_COMMAND)
            .about("formats to a skype call QR-Code")
            .arg(Arg::with_name("name")
                .required(true)
                .value_name("HANDLE")))
        .subcommand(SubCommand::with_name(WHATSAPP_COMMAND)
            .about("formats to a whatsapp message QR-Code")
            .arg(Arg::with_name("message")
                .required(true)
                .value_name("MESSAGE")))
        .subcommand(SubCommand::with_name(SMS_COMMAND)
            .about("formats to a sms message QR-Code")
            .arg(Arg::with_name("number").required(true))
            .arg(Arg::with_name("subject").default_value(""))
            .arg(Arg::with_name("encoding")
                .possible_values(&["SMS", "SMSTO", "SMS_iOS"])
                .default_value("SMS")))
        .subcommand(SubCommand::with_name(MMS_COMMAND)
            .about("formats to a mms message QR-Code")
            .arg(Arg::with_name("number").required(true))
            .arg(Arg::with_name("subject").default_value(""))
            .arg(Arg::with_name("encoding")
                .possible_values(&["MMS", "MMSTO"])
                .default_value("MMS")))
        .subcommand(SubCommand::with_name(GEO_COMMAND)
            .about("formats to a geospacial location QR-Code")
            .arg(Arg::with_name("latitude").required(true))
            .arg(Arg::with_name("longitude").required(true))
            .arg(Arg::with_name("encoding")
                .possible_values(&["GEO", "GoogleMaps"])
                .default_value("GEO")))
        .subcommand(SubCommand::with_name(BOOKMARK_COMMAND)
            .about("formats to a bookmark QR-Code")
            .arg(Arg::with_name("title").required(true))
            .arg(Arg::with_name("url").required(true)))
}
<commit_msg>updated todo<commit_after>#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate clap;

extern crate qrcode;
extern crate termion;
extern crate image;
extern crate regex;
extern crate urlparse;

use clap::{App, AppSettings, Arg, Shell, SubCommand};
use qrcode::{QrCode, EcLevel};
use termion::color;
use image::GrayImage;
use std::process::exit;
use std::fs;

mod payloads;

const WIFI_COMMAND: &'static str = "wifi";
const MAIL_COMMAND: &'static str = "mail";
const SMS_COMMAND: &'static str = "sms";
const MMS_COMMAND: &'static str = "mms";
const GEO_COMMAND: &'static str = "geo";
const PHONE_COMMAND: &'static str = "phone";
const SKYPE_COMMAND: &'static str = "skype";
const WHATSAPP_COMMAND: &'static str = "whatsapp";
const URL_COMMAND: &'static str = "url";
const BOOKMARK_COMMAND: &'static str = "bookmark";
// const BITCOIN_COMMAND: &'static str = "bitcoin";
// const GIRO_COMMAND: &'static str = "giro";
// const CALENDAR_COMMAND: &'static str = "calendar";

/*TODO: add arguments for:
- Add help texts for the subcommands
- Add error texts for some exit branches (mostly file output and qrcode gen)
- implement different types of qr payloads
- write unit tests for the payloads
- write integration tests for edge case inputs
*/
fn main() {
    // match all input args
    let matches = build_cli().get_matches();

    // write the completions if they were requested, then exit and dont print any qr-code
    if let Some(comp) = matches.subcommand_matches("completions") {
        let dir = comp.value_of("comp_dir").unwrap();

        let shell = match comp.value_of("shell") {
            Some("ps") => Shell::PowerShell,
            Some("zsh") => Shell::Zsh,
            Some("fish") => Shell::Fish,
            _ => Shell::Bash,
        };

        // create directory if necessary
        fs::create_dir_all(&dir).unwrap();

        let mut app = build_cli();
        app.gen_completions("qr", shell, dir);

        println!("Completion file for the {:?} shell was writen to: {:?}", shell, dir);

        // exit and dont generate a qr-code
        exit(0);
    }

    // deduce the string payload
    let payload = get_payload(&matches);

    // should we draw a white border (safe zone) around the code?
    let safe: bool = match matches.occurrences_of("safe_zone") {
        0 => true,
        _ => false,
    };

    // what error level can we expect? always defaults to "H"
    let error: EcLevel = match matches.value_of("error").unwrap() {
        "L" => EcLevel::L,
        "M" => EcLevel::M,
        "Q" => EcLevel::Q,
        &_ => EcLevel::H,
    };

    //TODO: catch the possible errors
    let code = QrCode::with_error_correction_level(payload, error).unwrap();

    // are we drawing to the terminal or to a file?
    match matches.occurrences_of("output") {
        1 => save(&code, safe, matches.value_of("output").unwrap()),
        _ => draw(&code, safe),
    }
}

// deduces wich kind of string we are going to encode
fn get_payload(matches: &clap::ArgMatches) -> String {
    if let Some(sub) = matches.subcommand_matches(WIFI_COMMAND) {
        let auth = match sub.value_of("mode") {
            Some("WEP") => payloads::Authentication::WEP,
            Some("WPA") => payloads::Authentication::WPA,
            _ => payloads::Authentication::nopass,
        };
        return payloads::wifi_string(sub.value_of("ssid").unwrap(),
                                     sub.value_of("pwd").unwrap(),
                                     &auth,
                                     sub.value_of("hidden").unwrap() == "true");
    } else if let Some(sub) = matches.subcommand_matches(MAIL_COMMAND) {
        let encoding = match sub.value_of("encoding") {
            Some("MATMSG") => payloads::MailEncoding::MATMSG,
            Some("SMTP") => payloads::MailEncoding::SMTP,
            _ => payloads::MailEncoding::MAILTO,
        };
        return payloads::mail_string(sub.value_of("receiver").unwrap(),
                                     sub.value_of("subject").unwrap(),
                                     sub.value_of("message").unwrap(),
                                     &encoding);
    } else if let Some(sub) = matches.subcommand_matches(URL_COMMAND) {
        return payloads::url_string(sub.value_of("url").unwrap());
    } else if let Some(sub) = matches.subcommand_matches(PHONE_COMMAND) {
        return payloads::phone_string(sub.value_of("phone").unwrap());
    } else if let Some(sub) = matches.subcommand_matches(SKYPE_COMMAND) {
        return payloads::skype_string(sub.value_of("name").unwrap());
    } else if let Some(sub) = matches.subcommand_matches(WHATSAPP_COMMAND) {
        return payloads::whatsapp_string(sub.value_of("message").unwrap());
    } else if let Some(sub) = matches.subcommand_matches(SMS_COMMAND) {
        let encoding = match sub.value_of("encoding") {
            Some("SMSTO") => payloads::SMSEncoding::SMSTO,
            Some("SMS_iOS") => payloads::SMSEncoding::SMS_iOS,
            _ => payloads::SMSEncoding::SMS,
        };
        return payloads::sms_string(sub.value_of("number").unwrap(),
                                    sub.value_of("subject").unwrap(),
                                    &encoding);
    } else if let Some(sub) = matches.subcommand_matches(MMS_COMMAND) {
        let encoding = match sub.value_of("encoding") {
            Some("MMSTO") => payloads::MMSEncoding::MMSTO,
            _ => payloads::MMSEncoding::MMS,
        };
        return payloads::mms_string(sub.value_of("number").unwrap(),
                                    sub.value_of("subject").unwrap(),
                                    &encoding);
    } else if let Some(sub) = matches.subcommand_matches(GEO_COMMAND) {
        let encoding = match sub.value_of("encoding") {
            Some("GoogleMaps") => payloads::GeolocationEncoding::GoogleMaps,
            _ => payloads::GeolocationEncoding::GEO,
        };
        return payloads::geo_string(sub.value_of("latitude").unwrap(),
                                    sub.value_of("longitude").unwrap(),
                                    &encoding);
    } else if let Some(sub) = matches.subcommand_matches(BOOKMARK_COMMAND) {
        return payloads::bookmark_string(sub.value_of("title").unwrap(),
                                         sub.value_of("url").unwrap());
    } else {
        return String::from(matches.value_of("INPUT").unwrap());
    }
}

// save to a file at the path
fn save(code: &QrCode, safe: bool, path: &str) {
    // render to a image struct
    let image: GrayImage = code.render().quiet_zone(safe).to_image();

    // save the image
    match image.save(path) {
        Ok(..) => println!("Image successfully saved to: {:?}", path),
        Err(e) => println!("Tried to create file but there was a problem: {:?}",
            if let Some(inner_err) = e.get_ref() { inner_err.to_string() } else { e.to_string() }),
    };
}

// draw to the terminal
fn draw(code: &QrCode, safe: bool) {
    // get "bit" array
    let bit_array = code.to_vec();

    // get the code width and add extra space for the safe zone
    let w = code.width();
    let wide = w + 6;

    // draw the first white safe zone
    if safe {
        for a in 1..(wide * 3) + 1 {
            print!("{}  ", color::Bg(color::LightWhite));
            if a % wide == 0 {
                println!("{}", color::Bg(color::Reset));
            }
        }
    }

    // main drawing loop
    for (i, item) in bit_array.iter().enumerate() {
        // left safe zone
        if safe && i % w == 0 {
            print!("{}      ", color::Bg(color::LightWhite));
        }

        // draw black or white blocks
        if *item {
            print!("{}  ", color::Bg(color::Black));
        } else {
            print!("{}  ", color::Bg(color::LightWhite));
        }

        if (i + 1) % w == 0 {
            if safe {
                //draw right safe zone
                println!("{}      {}",
                         color::Bg(color::LightWhite),
                         color::Bg(color::Reset));
            } else {
                println!("{}", color::Bg(color::Reset));
            }
        }
    }

    //Draw the last white safe zone
    if safe {
        for a in 1..(wide * 3) + 1 {
            print!("{}  ", color::Bg(color::LightWhite));
            if a % wide == 0 {
                println!("{}", color::Bg(color::Reset));
            }
        }
    }
}

// create the interface for the app with all subcommands, flags and args
fn build_cli() -> App<'static, 'static> {
    App::new(crate_name!())
        .version(crate_version!())
        .author(crate_authors!("\n"))
        .about(crate_description!())
        .global_setting(AppSettings::SubcommandsNegateReqs)
        .arg(Arg::with_name("safe_zone")
            .global(true)
            .short("s").long("safe-zone")
            .help("Sets wether the safe zone around the code should be drawn or not.")
            .takes_value(false))
        .arg(Arg::with_name("output")
            .global(true)
            .short("o").long("output")
            .help("Prints the QR-Code to a file. The image format is derived from the file extension. Currently only jpeg and png files are supported.")
            .value_name("FILE"))
        .arg(Arg::with_name("error")
            .global(true)
            .short("e").long("error")
            .help("Set the desired error correction level.")
            .value_name("LEVEL")
            .possible_values(&["L", "M", "Q", "H"])
            .default_value("H"))
        .arg(Arg::with_name("INPUT")
            .help("The input string to use")
            .required(true))
        .subcommand(SubCommand::with_name("completions")
            .about("Outputs completion files for various shells.")
            .arg(Arg::with_name("comp_dir")
                .required(true)
                .help("The directory to write the completion file to.")
                .value_name("DIR"))
            .arg(Arg::with_name("shell")
                .long("shell")
                .help("For which shell the completions should be generated for.")
                .value_name("SHELL")
                .possible_values(&["bash", "zsh", "fish", "ps"])
                .default_value("bash")))
        .subcommand(SubCommand::with_name(WIFI_COMMAND)
            .about("formats to a wifi access string QR-Code")
            .arg(Arg::with_name("ssid").required(true))
            .arg(Arg::with_name("pwd").required(true))
            .arg(Arg::with_name("mode")
                .value_name("MODE")
                .possible_values(&["WEP", "WPA", "nopass"])
                .default_value("WPA"))
            .arg(Arg::with_name("hidden")
                .value_name("HIDDEN")
                .possible_values(&["true", "false"])
                .default_value("false")))
        .subcommand(SubCommand::with_name(MAIL_COMMAND)
            .about("formats to a mail adress string QR-Code")
            .arg(Arg::with_name("receiver").required(true))
            .arg(Arg::with_name("subject"))
            .arg(Arg::with_name("message"))
            .arg(Arg::with_name("encoding")
                .value_name("ENCODING")
                .possible_values(&["MAILTO", "MATMSG", "SMTP"])
                .default_value("MAILTO")))
        .subcommand(SubCommand::with_name(URL_COMMAND)
            .about("formats to an URL QR-Code")
            .arg(Arg::with_name("url")
                .required(true)
                .value_name("URL")))
        .subcommand(SubCommand::with_name(PHONE_COMMAND)
            .about("formats to a phone number QR-Code")
            .arg(Arg::with_name("number")
                .required(true)
                .value_name("NUMBER")))
        .subcommand(SubCommand::with_name(SKYPE_COMMAND)
            .about("formats to a skype call QR-Code")
            .arg(Arg::with_name("name")
                .required(true)
                .value_name("HANDLE")))
        .subcommand(SubCommand::with_name(WHATSAPP_COMMAND)
            .about("formats to a whatsapp message QR-Code")
            .arg(Arg::with_name("message")
                .required(true)
                .value_name("MESSAGE")))
        .subcommand(SubCommand::with_name(SMS_COMMAND)
            .about("formats to a sms message QR-Code")
            .arg(Arg::with_name("number").required(true))
            .arg(Arg::with_name("subject").default_value(""))
            .arg(Arg::with_name("encoding")
                .possible_values(&["SMS", "SMSTO", "SMS_iOS"])
                .default_value("SMS")))
        .subcommand(SubCommand::with_name(MMS_COMMAND)
            .about("formats to a mms message QR-Code")
            .arg(Arg::with_name("number").required(true))
            .arg(Arg::with_name("subject").default_value(""))
            .arg(Arg::with_name("encoding")
                .possible_values(&["MMS", "MMSTO"])
                .default_value("MMS")))
        .subcommand(SubCommand::with_name(GEO_COMMAND)
            .about("formats to a geospacial location QR-Code")
            .arg(Arg::with_name("latitude").required(true))
            .arg(Arg::with_name("longitude").required(true))
            .arg(Arg::with_name("encoding")
                .possible_values(&["GEO", "GoogleMaps"])
                .default_value("GEO")))
        .subcommand(SubCommand::with_name(BOOKMARK_COMMAND)
            .about("formats to a bookmark QR-Code")
            .arg(Arg::with_name("title").required(true))
            .arg(Arg::with_name("url").required(true)))
}
<|endoftext|>


<commit_before>extern crate hyper;
extern crate hyper_openssl;
extern crate hubcaps;
extern crate chrono;
extern crate rand;
extern crate reqwest;
extern crate docopt;

#[macro_use]
extern crate error_chain;

#[macro_use]
extern crate serde_derive;

use std::fs::File;
use std::io::{self, BufRead, BufReader, Write};
use std::process::{Command, Stdio};
use std::fmt;

use hyper::Client;
use hyper::net::HttpsConnector;
use hyper_openssl::OpensslClient;

use hubcaps::{Credentials, Github};
use hubcaps::repositories::Repository;
use hubcaps::rep::{IssueListOptionsBuilder, Issue};

use chrono::{DateTime, UTC};

use docopt::Docopt;

const USAGE: &'static str = "
nag-rs

Usage:
  nag-rs [-d] <token> <file>

Options:
  -d       Dry run; prints emails to stdout
";

error_chain! {
    foreign_links {
        Hubcaps(::hubcaps::Error);
        Reqwest(::reqwest::Error);
        Io(::std::io::Error);
    }
}

#[derive(Debug, Deserialize)]
struct FcpItem {
    issue: FcpIssue,
}

#[derive(Debug, Deserialize)]
struct FcpIssue {
    repository: String,
    number: u64,
    title: String,
    updated_at: String,
}

#[derive(Debug, Deserialize)]
struct FcpUser {
    login: String,
}

#[derive(Debug, Deserialize)]
struct FcpList(FcpUser, Vec<FcpItem>);

#[derive(Debug)]
enum Kind {
    FCP,
    RFC,
    PR,
}

struct Item {
    number: u64,
    kind: Kind,
    url: String,
    title: String,
    last_update: DateTime<UTC>,
}

impl Item {
    fn from_issue(issue: &Issue, kind: Kind) -> Item {
        Item {
            number: issue.number,
            kind: kind,
            url: issue.html_url.clone(),
            title: issue.title.clone(),
            last_update: issue.updated_at.parse()
                .unwrap_or(UTC::now()),
        }
    }

    fn from_fcp(item: &FcpItem) -> Item {
        Item {
            number: item.issue.number,
            kind: Kind::FCP,
            url: format!("https://github.com/{}/issues/{}",
                         item.issue.repository,
                         item.issue.number),
            title: item.issue.title.clone(),
            last_update: (item.issue.updated_at.clone() + "Z").parse()
                .unwrap_or(UTC::now()),
        }
    }
}

impl fmt::Display for Item {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "
{:?}: {}
Last update: {}",
               self.kind, self.url, self.title, self.last_update.date().format("%Y-%m-%d"))
    }
}

#[derive(Clone)]
struct TeamMember {
    login: String,
    email: String,
}

impl TeamMember {
    fn items(&self, rust: &Repository, rfcs: &Repository) -> Result<Vec<Item>> {
        let filter = IssueListOptionsBuilder::new()
            .assignee(&self.login[..])
            .build();
        let mut items: Vec<Item> = rust.issues().list(&filter)?
            .iter()
            .map(|issue| Item::from_issue(issue, Kind::PR))
            .filter(|item| item.url.contains("pull"))
            .collect();

        let filter = IssueListOptionsBuilder::new()
            .assignee(&self.login[..])
            .build();
        items.extend(rfcs.issues().list(&filter)?
                     .iter()
                     .map(|issue| Item::from_issue(issue, Kind::RFC))
                     .filter(|item| item.url.contains("pull")));

        let mut url = String::from("http://rusty-dash.com/api/fcp/");
        url.push_str(&self.login);

        let mut resp = reqwest::get(&url)?;
        let list: FcpList = resp.json()?;
        items.extend(list.1.iter().map(Item::from_fcp));

        // wait until Rust 1.16
        //items.sort_by_key(|item| item.number);
        //items.dedup_by_key(|item| item.number);
        items.sort_by_key(|item| item.last_update);

        Ok(items)
    }

    fn process(&self, dry_run: bool, rust: &Repository, rfcs: &Repository) -> Result<()> {
        let items = self.items(rust, rfcs)?;
        if !items.is_empty() {
            if dry_run {
                write_email(&mut io::stdout(), self, items)
            } else {
                send_email(self, items)
            }
        } else {
            Ok(())
        }
    }
}

fn write_email<W: Write>(f: &mut W, member: &TeamMember, mut items: Vec<Item>)
                         -> Result<()>
{
    writeln!(f, "\
Subject: Rust review list for {date}
From: {from}
To: {to}
MIME-Version: 1.0
Content-Type: text/html

Hello Rust subteam member! You have {total} items awaiting review.
This email contains your reviewing mission for today.
",
                 date=UTC::now().format("%Y-%m-%d"),
                 total=items.len(),
                 from="nagbot@rust-lang.org",
                 to=member.email)?;

    const STALE_COUNT: usize = 3;
    const RAND_COUNT: usize = 2;

    if items.len() > STALE_COUNT + RAND_COUNT {
        writeln!(f, "
Three stalest items:")?;
        for item in items.drain(..STALE_COUNT) {
            writeln!(f, "{}", item)?
        }

        writeln!(f, "
Three random items:")?;
        for _ in 0..RAND_COUNT {
            let len = items.len();
            if len == 0 { break; }
            let item = items.swap_remove(rand::random::<usize>() % len);
            writeln!(f, "{}", item)?
        }
    } else {
        writeln!(f, "
All items:")?;
        for item in items {
            writeln!(f, "{}", item)?
        }
    }

    Ok(())
}

fn send_email(member: &TeamMember, items: Vec<Item>) -> Result<()> {
    let mut child = Command::new("sendmail").arg("-t")
        .stdin(Stdio::piped())
        .spawn()?;

    write_email(&mut io::BufWriter::new(child.stdin.as_mut().unwrap()), member, items)?;

    let status = child.wait()?;
    if !status.success() {
        panic!()
    }

    Ok(())
}

fn run(dry_run: bool, token: &str, input_file: &str) -> Result<()> {
    let ssl = OpensslClient::new().chain_err(|| "couldn't set up SSL")?;
    let connector = HttpsConnector::new(ssl);
    let client = Client::with_connector(connector);
    let github = Github::new(
        "nag-rs",
        client,
        Credentials::Token(token.to_owned())
    );

    let rust = github.repo("rust-lang", "rust");
    let rfcs = github.repo("rust-lang", "rfcs");
    let input = File::open(input_file)
        .chain_err(|| format!("couldn't open input file {}", input_file))?;

    for line in BufReader::new(input).lines() {
        let line = line.chain_err(|| "couldn't read line")?;
        let mut line_iter = line.split(",");
        let member= TeamMember {
            login: line_iter.next().ok_or("malformed input")?.to_owned(),
            email: line_iter.next().ok_or("malformed input")?.to_owned(),
        };

        print_if_err(member.process(dry_run, &rust, &rfcs),
                     &format!("error for {}", member.login));
    }

    Ok(())
}

fn print_if_err(res: Result<()>, msg: &str) {
    if let Err(ref e) = res {
        writeln!(io::stderr(), "{}: {}", e, msg).unwrap();

        for e in e.iter().skip(1) {
            writeln!(io::stderr(), "caused by: {}", e).unwrap();
        }
    }
}

fn main() {
    let args = Docopt::new(USAGE)
        .and_then(|dopt| dopt.parse())
        .unwrap_or_else(|e| e.exit());

    let res = run(args.get_bool("-d"),
                  &args.get_str("<token>"),
                  &args.get_str("<file>"));
    print_if_err(res, "error");
}
<commit_msg>Fix up text a bit<commit_after>extern crate hyper;
extern crate hyper_openssl;
extern crate hubcaps;
extern crate chrono;
extern crate rand;
extern crate reqwest;
extern crate docopt;

#[macro_use]
extern crate error_chain;

#[macro_use]
extern crate serde_derive;

use std::fs::File;
use std::io::{self, BufRead, BufReader, Write};
use std::process::{Command, Stdio};
use std::fmt;

use hyper::Client;
use hyper::net::HttpsConnector;
use hyper_openssl::OpensslClient;

use hubcaps::{Credentials, Github};
use hubcaps::repositories::Repository;
use hubcaps::rep::{IssueListOptionsBuilder, Issue};

use chrono::{DateTime, UTC};

use docopt::Docopt;

const USAGE: &'static str = "
nag-rs

Usage:
  nag-rs [-d] <token> <file>

Options:
  -d       Dry run; prints emails to stdout
";

error_chain! {
    foreign_links {
        Hubcaps(::hubcaps::Error);
        Reqwest(::reqwest::Error);
        Io(::std::io::Error);
    }
}

#[derive(Debug, Deserialize)]
struct FcpItem {
    issue: FcpIssue,
}

#[derive(Debug, Deserialize)]
struct FcpIssue {
    repository: String,
    number: u64,
    title: String,
    updated_at: String,
}

#[derive(Debug, Deserialize)]
struct FcpUser {
    login: String,
}

#[derive(Debug, Deserialize)]
struct FcpList(FcpUser, Vec<FcpItem>);

#[derive(Debug)]
enum Kind {
    FCP,
    RFC,
    PR,
}

struct Item {
    number: u64,
    kind: Kind,
    url: String,
    title: String,
    last_update: DateTime<UTC>,
}

impl Item {
    fn from_issue(issue: &Issue, kind: Kind) -> Item {
        Item {
            number: issue.number,
            kind: kind,
            url: issue.html_url.clone(),
            title: issue.title.clone(),
            last_update: issue.updated_at.parse()
                .unwrap_or(UTC::now()),
        }
    }

    fn from_fcp(item: &FcpItem) -> Item {
        Item {
            number: item.issue.number,
            kind: Kind::FCP,
            url: format!("https://github.com/{}/issues/{}",
                         item.issue.repository,
                         item.issue.number),
            title: item.issue.title.clone(),
            last_update: (item.issue.updated_at.clone() + "Z").parse()
                .unwrap_or(UTC::now()),
        }
    }
}

impl fmt::Display for Item {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "
{:?}: {}
Last update: {}",
               self.kind, self.url, self.title, self.last_update.date().format("%Y-%m-%d"))
    }
}

#[derive(Clone)]
struct TeamMember {
    login: String,
    email: String,
}

impl TeamMember {
    fn items(&self, rust: &Repository, rfcs: &Repository) -> Result<Vec<Item>> {
        let filter = IssueListOptionsBuilder::new()
            .assignee(&self.login[..])
            .build();
        let mut items: Vec<Item> = rust.issues().list(&filter)?
            .iter()
            .map(|issue| Item::from_issue(issue, Kind::PR))
            .filter(|item| item.url.contains("pull"))
            .collect();

        let filter = IssueListOptionsBuilder::new()
            .assignee(&self.login[..])
            .build();
        items.extend(rfcs.issues().list(&filter)?
                     .iter()
                     .map(|issue| Item::from_issue(issue, Kind::RFC))
                     .filter(|item| item.url.contains("pull")));

        let mut url = String::from("http://rusty-dash.com/api/fcp/");
        url.push_str(&self.login);

        let mut resp = reqwest::get(&url)?;
        let list: FcpList = resp.json()?;
        items.extend(list.1.iter().map(Item::from_fcp));

        // wait until Rust 1.16
        //items.sort_by_key(|item| item.number);
        //items.dedup_by_key(|item| item.number);
        items.sort_by_key(|item| item.last_update);

        Ok(items)
    }

    fn process(&self, dry_run: bool, rust: &Repository, rfcs: &Repository) -> Result<()> {
        let items = self.items(rust, rfcs)?;
        if !items.is_empty() {
            if dry_run {
                write_email(&mut io::stdout(), self, items)
            } else {
                send_email(self, items)
            }
        } else {
            Ok(())
        }
    }
}

fn write_email<W: Write>(f: &mut W, member: &TeamMember, mut items: Vec<Item>)
                         -> Result<()>
{
    writeln!(f, "\
Subject: Rust review list for {date}
From: {from}
To: {to}
MIME-Version: 1.0
Content-Type: text/html

Hello Rust subteam member! You have {total} items awaiting review.
This email contains your reviewing mission for today.
",
                 date=UTC::now().format("%Y-%m-%d"),
                 total=items.len(),
                 from="nagbot@rust-lang.org",
                 to=member.email)?;

    const STALE_COUNT: usize = 3;
    const RAND_COUNT: usize = 2;

    if items.len() > STALE_COUNT + RAND_COUNT {
        writeln!(f, "
Stale items:")?;
        for item in items.drain(..STALE_COUNT) {
            writeln!(f, "{}", item)?
        }

        writeln!(f, "
Random items:")?;
        for _ in 0..RAND_COUNT {
            let len = items.len();
            if len == 0 { break; }
            let item = items.swap_remove(rand::random::<usize>() % len);
            writeln!(f, "{}", item)?
        }
    } else {
        writeln!(f, "
All items:")?;
        for item in items {
            writeln!(f, "{}", item)?
        }
    }

    Ok(())
}

fn send_email(member: &TeamMember, items: Vec<Item>) -> Result<()> {
    let mut child = Command::new("sendmail").arg("-t")
        .stdin(Stdio::piped())
        .spawn()?;

    write_email(&mut io::BufWriter::new(child.stdin.as_mut().unwrap()), member, items)?;

    let status = child.wait()?;
    if !status.success() {
        panic!()
    }

    Ok(())
}

fn run(dry_run: bool, token: &str, input_file: &str) -> Result<()> {
    let ssl = OpensslClient::new().chain_err(|| "couldn't set up SSL")?;
    let connector = HttpsConnector::new(ssl);
    let client = Client::with_connector(connector);
    let github = Github::new(
        "nag-rs",
        client,
        Credentials::Token(token.to_owned())
    );

    let rust = github.repo("rust-lang", "rust");
    let rfcs = github.repo("rust-lang", "rfcs");
    let input = File::open(input_file)
        .chain_err(|| format!("couldn't open input file {}", input_file))?;

    for line in BufReader::new(input).lines() {
        let line = line.chain_err(|| "couldn't read line")?;
        let mut line_iter = line.split(",");
        let member= TeamMember {
            login: line_iter.next().ok_or("malformed input")?.to_owned(),
            email: line_iter.next().ok_or("malformed input")?.to_owned(),
        };

        print_if_err(member.process(dry_run, &rust, &rfcs),
                     &format!("error for {}", member.login));
    }

    Ok(())
}

fn print_if_err(res: Result<()>, msg: &str) {
    if let Err(ref e) = res {
        writeln!(io::stderr(), "{}: {}", e, msg).unwrap();

        for e in e.iter().skip(1) {
            writeln!(io::stderr(), "caused by: {}", e).unwrap();
        }
    }
}

fn main() {
    let args = Docopt::new(USAGE)
        .and_then(|dopt| dopt.parse())
        .unwrap_or_else(|e| e.exit());

    let res = run(args.get_bool("-d"),
                  &args.get_str("<token>"),
                  &args.get_str("<file>"));
    print_if_err(res, "error");
}
<|endoftext|>


<commit_before>fn main() {
    println!("Hello, world!");
}
<commit_msg>start sketching out types<commit_after>type FreeVariable = u64;
type VariableName<'a> = &'a str;

/// AST node expression
enum Expression<'a> {
    Number(f64),
    Boolean(bool),

    /// simple variable
    Variable(&'a VariableName<'a>),

    /// fn (x, y, z) -> expr
    Lambda(&'a [VariableName<'a>], &'a Expression<'a>),

    /// let x = expr in expr
    Let(VariableName<'a>, &'a Expression<'a>, &'a Expression<'a>),

    /// some_fn (x, y, z)
    FnCall(&'a Expression<'a>, &'a[Expression<'a>])
}

enum Type<'a> {
    Number,
    Boolean,
    Unbound(FreeVariable),
    Lambda(&'a [Type<'a>], &'a Type<'a>),

}

type TaggedType<'a> = (Expression<'a>, Type<'a>);

fn main() {
    println!("Hello, world!");
}
<|endoftext|>


<commit_before>//! # Hexdino
//!
//! A hex editor with vim like keybindings written in Rust.

#![doc(html_logo_url = "https://raw.githubusercontent.com/Luz/hexdino/master/logo.png")]

use std::io::prelude::*;
use std::fs::OpenOptions;
use std::io::SeekFrom;
use std::path::Path;
use std::error::Error;
use std::env;

extern crate ncurses;
use ncurses::*;

//extern crate getopts;
//use getopts::Options;

fn main() {
    let mut buf = vec![];
    let mut cursorpos:usize = 0;
    let mut cursorstate:usize = 0; //0 is left nibble, 1 is right nibble, 2 is ascii
    let mut screenoffset:usize = 0; // 0 = display data from first line of file
    const SPALTEN:usize = 16;
    let mut command = String::new();

    initscr(); //start ncursesw
    let screenheight : usize = getmaxy(stdscr) as usize;
    cbreak();  //ctrl+z and fg works with this
    noecho();
    start_color();
    init_pair(1, COLOR_GREEN, COLOR_BLACK);

    printw("Welcome to Hexdino.\nPlease find the any-key on your keyboard and press it.\n");

    let args: Vec<_> = env::args().collect();
    /*
    let program = args[0].clone();
    let mut opts = Options::new();
    opts.optopt("f", "", "set file name", "NAME");
    opts.optflag("h", "help", "print this help menu");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m }
        Err(f) => { panic!(f.to_string()) }
    };
    if matches.opt_present("h") {
        let brief = format!("Usage: {} FILE [options]", program);
            print!("{}", opts.usage(&brief));
        return;
    }
    let path = if !matches.free.is_empty() {
        matches.free[0].clone()
    } else {
        let brief = format!("Usage: {} FILE [options]", program);
            print!("{}", opts.usage(&brief));
        return;
    };*/

    let path = if args.len() > 1 {
        printw(&format!("Opening file {}", args[1]));
        args[1].clone()
    } else {
        printw(&format!("No file specified. Trying to open foo.txt"));
        "foo.txt".into()
    };

    let path = Path::new(&path);
    let display = path.display();

    getch();
    clear();

    if !has_colors() {
        endwin();
        println!("Your terminal does not support color!\n");
        return;
    }

//TODO in this order: 1)disp files 2) hjkl movement 3) edit file by 'r' 4) save file 5) edit file by 'x, i' 6) search by '/'

    let mut file = match OpenOptions::new().read(true).write(true).create(true).open(&path) {
        Err(why) => panic!("couldn't open {}: {}", display,
            Error::description(&why)),
        Ok(file) => file,
    };

    file.read_to_end(&mut buf).ok().expect("File could not be read.");

    let mut mode = 0; // 0 Command mode, 1 replace next char, 2 type a command, 3 Insert

    draw(&buf, cursorpos, SPALTEN, screenheight, mode, &command, cursorstate, screenoffset);

    let mut key;
//    key = getch() as u8;
//    printw(&format!("{:?}", key));

    let mut quitnow = 0;
    while quitnow == 0 {
        key = getch() as u8;
        if mode == 0 { // movement mode
            match key{
                104 => { // Button "h"
                    if cursorstate == 2 { // ascii mode
                        if cursorpos > 0 { // not at start
                            cursorpos-=1; // go left
                        }
                    }
                    else if cursorstate == 1 { // hex mode, right nibble
                        cursorstate = 0; // left nibble
                    }
                    else if cursorstate == 0 { // hex mode, left nibble
                        if cursorpos > 0 { // not at start
                            cursorstate = 1; // right nibble
                            cursorpos-=1; // go left
                        }
                    }
                },
                106 => { //Button "j"
                    if cursorpos+SPALTEN < buf.len() { // not at end
                        cursorpos+=SPALTEN; // go down
                    }
                    else { // when at end
                        cursorpos=buf.len()-1; // go to end
                    }
                },
                107 => { //Button "k"
                    if cursorpos >= SPALTEN {
                        cursorpos-=SPALTEN;
                    }
                },
                108 => { // Button "l"
                    if cursorstate == 2 { // ascii mode
                        if cursorpos < buf.len()-1 { // not at end
                            cursorpos+=1; // go right
                        }
                    }
                    else if cursorstate == 0 { // hex mode, left nibble
                        cursorstate = 1; // right nibble
                    }
                    else if cursorstate == 1 { // hex mode, right nibble
                        if cursorpos < buf.len()-1 { // not at end
                            cursorstate = 0; // left nibble
                            cursorpos+=1; // go right
                        }
                    }
                },
                 48 => { // Button "0"
                    cursorpos -= cursorpos%SPALTEN; // jump to start of line
                    if cursorstate == 1 { // hex mode, right nibble
                        cursorstate = 0; // left nibble
                    }
                },
                 36 => { // Button "$"
                    if cursorpos-(cursorpos%SPALTEN)+(SPALTEN-1) < buf.len() { // check if no overflow
                        cursorpos = cursorpos-(cursorpos%SPALTEN)+(SPALTEN-1); // jump to end of line
                    }
                    else {
                        cursorpos = buf.len()-1 // jump to end of line
                    }
                    if cursorstate == 0 { // hex mode, left nibble
                        cursorstate = 1; // right nibble
                    }
                },
                114 => { // Button "r"
                    mode = 1; // replaces the next char
                },
                120 => { // Button "x"
                    if buf.len() > 0 { // remove the next char
                        buf.remove(cursorpos);
                    }
                },
                105 => { // Button "i"
                    mode = 3; // go to insert mode
                },
                 58 => { // Button ":"
                     command.clear(); // delete old command
                     mode = 2; // write a command
                 },
                 74 => { // Button "J"
                     if cursorstate == 2 { // ascii mode
                         cursorstate = 0; // hex mode, left nibble
                     }
                     else {
                         cursorstate = 2; // jump to ascii
                     }
                 },
                 63 => { //TODO
                     command.push_str("No helpfile yet");
                 },
                 47 => { //TODO
                     cursorpos = buf.iter().position(|&x| x == 0x33).unwrap();
                 },
                _ => (),
            }

            //Always move screen when cursor leaves screen
            if cursorpos > (screenheight+screenoffset-1)*SPALTEN -1 {
                screenoffset+=1; // move screen
            }
            if cursorpos < screenoffset*SPALTEN {
                screenoffset-=1; // move screen
            }

        } else
        if mode == 1 && cursorstate == 2 { // r was pressed so replace next char in ascii mode
            match key {
                c @ 32...126 => { buf[cursorpos] = c },
                _ => (),
            }
            mode = 0;
        } else
        if mode == 1 {
            let mask = if cursorstate == 0 { 0x0F } else { 0xF0 };
            let shift = if cursorstate == 0 { 4 } else { 0 };
            match key { // Change the selected nibble
                c @ 65... 70 => // A-F
                    { buf[cursorpos] = buf[cursorpos]&mask | (c-55)<<shift },
                c @ 97...102 => // a-f
                    { buf[cursorpos] = buf[cursorpos]&mask | (c-87)<<shift },
                c @ 48... 57 => // 0-9
                    { buf[cursorpos] = buf[cursorpos]&mask | (c-48)<<shift },
                _ => ()
            }
            mode = 0;
        } else
        if mode == 2 {
            match key {
                c @ 32...126 => { command.push(c as char); },
                27 => {command.clear();mode = 0;},
                10 => { // Enter pressed, compute command!
                        if (command == "w".to_string()) || (command == "wq".to_string()) {
                            file.seek(SeekFrom::Start(0)).ok().expect("Filepointer could not be set to 0");
                            file.write_all(&mut buf).ok().expect("File could not be written.");
                            if command == "wq".to_string() {
                                quitnow = 1;
                            }
                            command.clear();mode = 0;
                        }
                        else if command == "q".to_string() {
                            quitnow = 1;
                        }
                        else {
                            command.clear();
                            command.push_str("Bad_command!");
                            mode = 0;
                        }
                    },
                _ => (),
            }
        } else
        if mode == 3 {
            if cursorstate == 0 { // Left nibble
                match key {
                    c @ 65... 70 => // A-F
                        {buf.insert(cursorpos, (c-55)<<4 ); cursorstate = 1;},
                    c @ 97...102 => // a-f
                        {buf.insert(cursorpos, (c-87)<<4 ); cursorstate = 1;},
                    c @ 48... 57 => // 0-9
                        {buf.insert(cursorpos, (c-48)<<4 ); cursorstate = 1;},
                    27 => {mode = 0;},
                    _ => ()
                }
            } else
            if cursorstate == 1 { // Right nibble
                match key {
                    c @ 65...70 => // A-F
                        { buf[cursorpos] = buf[cursorpos]&0xF0 | (c-55); cursorstate = 0; cursorpos+=1; },
                    c @ 97...102 => // a-f
                        { buf[cursorpos] = buf[cursorpos]&0xF0 | (c-87); cursorstate = 0; cursorpos+=1; },
                    c @ 48...57 => // 0-9
                        { buf[cursorpos] = buf[cursorpos]&0xF0 | (c-48); cursorstate = 0; cursorpos+=1; },
                    27 => {mode = 0;},
                    _ => ()
                }
            } else
            if cursorstate == 2 { // Ascii
                match key {
                    c @ 32...126 => { buf.insert(cursorpos, c); cursorpos+=1; },
                    27 => {mode = 0;},
                    _ => (),
                }
            }
        }
        draw(&buf, cursorpos, SPALTEN, screenheight, mode, &command, cursorstate, screenoffset);
    }

    refresh();
    endwin();
}

fn draw(buf:&Vec<u8>, cursorpos:usize, spalten:usize, maxzeilen:usize, mode:usize, command:&String, cursorstate:usize, screenoffset:usize) {
    erase();

    let mut zeilen = buf.len() / spalten;
    if zeilen >= maxzeilen-1 { // Last line reserved for Status/Commands/etc (Like in vim)
        zeilen = maxzeilen-2;
    }

    for z in 0 .. zeilen+1 {
        printw(&format!("{:08X}: ", z*spalten+screenoffset*spalten )); // 8 hex digits (4GB/spalten or 0.25GB@spalten=SPALTEN)
        printw(" "); // Additional space between line number and hex
        for s in 0 .. spalten {
            if z*spalten+screenoffset*spalten+s < buf.len() {
                if z*spalten+screenoffset*spalten+s == cursorpos { // Color of left nibble
                    if cursorstate == 0 {attron(COLOR_PAIR(1) | A_STANDOUT());}
                    else if cursorstate == 2 {attron(A_UNDERLINE());}
                }
                printw(&format!("{:01X}", buf[z*spalten+screenoffset*spalten+s]>>4) ); // Display left nibble
                if z*spalten+screenoffset*spalten+s == cursorpos { // End of color left nibble
                    if cursorstate == 0 {attroff(COLOR_PAIR(1) | A_STANDOUT());}
                    else if cursorstate == 2 {attroff(A_UNDERLINE());}
                }

                if z*spalten+screenoffset*spalten+s == cursorpos { // Color of right nibble
                    if cursorstate == 1 {attron(COLOR_PAIR(1) | A_STANDOUT());}
                    else if cursorstate == 2 {attron(A_UNDERLINE());}
                }
                printw(&format!("{:01X}", buf[z*spalten+screenoffset*spalten+s]&0x0F) ); // Display right nibble
                if z*spalten+screenoffset*spalten+s == cursorpos {
                    if cursorstate == 1 {attroff(COLOR_PAIR(1) | A_STANDOUT());}
                    else if cursorstate == 2 {attroff(A_UNDERLINE());}
                }
                printw(" ");
            } else {
                printw("-- ");
            }
        }
        printw(" "); // Additional space between hex and ascii
        for s in 0 .. spalten {
            if z*spalten+screenoffset*spalten+s == cursorpos {
                if cursorstate == 2 {attron(COLOR_PAIR(1) | A_STANDOUT());}
                else {attron(A_UNDERLINE());}
            }
            if z*spalten+screenoffset*spalten+s < buf.len() {
                if let c @ 32...126 = buf[z*spalten+screenoffset*spalten+s] {
                    if c as char == '%' {
                        printw("%%"); // '%' needs to be escaped by a '%' in ncurses
                    } else {
                        printw(&format!("{}", c as char) );
                    }
                }
                else {printw(&format!(".") );}
            }
            if z*spalten+screenoffset*spalten+s == cursorpos {
                if cursorstate == 2 {attroff(COLOR_PAIR(1) | A_STANDOUT());}
                else {attroff(A_UNDERLINE());}
            }
        }
        printw("\n");
    }
    for _ in 0 .. maxzeilen-zeilen-2 {
        printw("\n"); // Put the cursor on last line of terminal
    }
    if mode == 2 {
        printw(":"); // Indicate that a command can be typed in
    }
    if mode == 3 {
        printw("insert"); // Indicate that insert mode is active
    }
    printw(&format!("{}", command));
}
<commit_msg>Suppress overflow for 'remove' and 'move down'<commit_after>//! # Hexdino
//!
//! A hex editor with vim like keybindings written in Rust.

#![doc(html_logo_url = "https://raw.githubusercontent.com/Luz/hexdino/master/logo.png")]

use std::io::prelude::*;
use std::fs::OpenOptions;
use std::io::SeekFrom;
use std::path::Path;
use std::error::Error;
use std::env;

extern crate ncurses;
use ncurses::*;

//extern crate getopts;
//use getopts::Options;

fn main() {
    let mut buf = vec![];
    let mut cursorpos:usize = 0;
    let mut cursorstate:usize = 0; //0 is left nibble, 1 is right nibble, 2 is ascii
    let mut screenoffset:usize = 0; // 0 = display data from first line of file
    const SPALTEN:usize = 16;
    let mut command = String::new();

    initscr(); //start ncursesw
    let screenheight : usize = getmaxy(stdscr) as usize;
    cbreak();  //ctrl+z and fg works with this
    noecho();
    start_color();
    init_pair(1, COLOR_GREEN, COLOR_BLACK);

    printw("Welcome to Hexdino.\nPlease find the any-key on your keyboard and press it.\n");

    let args: Vec<_> = env::args().collect();
    /*
    let program = args[0].clone();
    let mut opts = Options::new();
    opts.optopt("f", "", "set file name", "NAME");
    opts.optflag("h", "help", "print this help menu");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m }
        Err(f) => { panic!(f.to_string()) }
    };
    if matches.opt_present("h") {
        let brief = format!("Usage: {} FILE [options]", program);
            print!("{}", opts.usage(&brief));
        return;
    }
    let path = if !matches.free.is_empty() {
        matches.free[0].clone()
    } else {
        let brief = format!("Usage: {} FILE [options]", program);
            print!("{}", opts.usage(&brief));
        return;
    };*/

    let path = if args.len() > 1 {
        printw(&format!("Opening file {}", args[1]));
        args[1].clone()
    } else {
        printw(&format!("No file specified. Trying to open foo.txt"));
        "foo.txt".into()
    };

    let path = Path::new(&path);
    let display = path.display();

    getch();
    clear();

    if !has_colors() {
        endwin();
        println!("Your terminal does not support color!\n");
        return;
    }

//TODO in this order: 1)disp files 2) hjkl movement 3) edit file by 'r' 4) save file 5) edit file by 'x, i' 6) search by '/'

    let mut file = match OpenOptions::new().read(true).write(true).create(true).open(&path) {
        Err(why) => panic!("couldn't open {}: {}", display,
            Error::description(&why)),
        Ok(file) => file,
    };

    file.read_to_end(&mut buf).ok().expect("File could not be read.");

    let mut mode = 0; // 0 Command mode, 1 replace next char, 2 type a command, 3 Insert

    draw(&buf, cursorpos, SPALTEN, screenheight, mode, &command, cursorstate, screenoffset);

    let mut key;
//    key = getch() as u8;
//    printw(&format!("{:?}", key));

    let mut quitnow = 0;
    while quitnow == 0 {
        key = getch() as u8;
        if mode == 0 { // movement mode
            match key{
                104 => { // Button "h"
                    if cursorstate == 2 { // ascii mode
                        if cursorpos > 0 { // not at start
                            cursorpos-=1; // go left
                        }
                    }
                    else if cursorstate == 1 { // hex mode, right nibble
                        cursorstate = 0; // left nibble
                    }
                    else if cursorstate == 0 { // hex mode, left nibble
                        if cursorpos > 0 { // not at start
                            cursorstate = 1; // right nibble
                            cursorpos-=1; // go left
                        }
                    }
                },
                106 => { //Button "j"
                    if cursorpos+SPALTEN < buf.len() { // not at end
                        cursorpos+=SPALTEN; // go down
                    }
                    else { // when at end
                        if buf.len() != 0 { // Suppress underflow
                            cursorpos=buf.len()-1; // go to end
                        }
                    }
                },
                107 => { //Button "k"
                    if cursorpos >= SPALTEN {
                        cursorpos-=SPALTEN;
                    }
                },
                108 => { // Button "l"
                    if cursorstate == 2 { // ascii mode
                        if cursorpos < buf.len()-1 { // not at end
                            cursorpos+=1; // go right
                        }
                    }
                    else if cursorstate == 0 { // hex mode, left nibble
                        cursorstate = 1; // right nibble
                    }
                    else if cursorstate == 1 { // hex mode, right nibble
                        if cursorpos < buf.len()-1 { // not at end
                            cursorstate = 0; // left nibble
                            cursorpos+=1; // go right
                        }
                    }
                },
                 48 => { // Button "0"
                    cursorpos -= cursorpos%SPALTEN; // jump to start of line
                    if cursorstate == 1 { // hex mode, right nibble
                        cursorstate = 0; // left nibble
                    }
                },
                 36 => { // Button "$"
                    if cursorpos-(cursorpos%SPALTEN)+(SPALTEN-1) < buf.len() { // check if no overflow
                        cursorpos = cursorpos-(cursorpos%SPALTEN)+(SPALTEN-1); // jump to end of line
                    }
                    else {
                        cursorpos = buf.len()-1 // jump to end of line
                    }
                    if cursorstate == 0 { // hex mode, left nibble
                        cursorstate = 1; // right nibble
                    }
                },
                114 => { // Button "r"
                    mode = 1; // replaces the next char
                },
                120 => { // Button "x"
                    if buf.len() > 0 { // remove the current char
                        buf.remove(cursorpos);
                        if cursorpos >= buf.len() {
                            cursorpos = buf.len()-1;
                        }
                    }
                },
                105 => { // Button "i"
                    mode = 3; // go to insert mode
                },
                 58 => { // Button ":"
                     command.clear(); // delete old command
                     mode = 2; // write a command
                 },
                 74 => { // Button "J"
                     if cursorstate == 2 { // ascii mode
                         cursorstate = 0; // hex mode, left nibble
                     }
                     else {
                         cursorstate = 2; // jump to ascii
                     }
                 },
                 63 => { //TODO
                     command.push_str("No helpfile yet");
                 },
                 47 => { //TODO
                     cursorpos = buf.iter().position(|&x| x == 0x33).unwrap();
                 },
                _ => (),
            }

            //Always move screen when cursor leaves screen
            if cursorpos > (screenheight+screenoffset-1)*SPALTEN -1 {
                screenoffset+=1; // move screen
            }
            if cursorpos < screenoffset*SPALTEN {
                screenoffset-=1; // move screen
            }

        } else
        if mode == 1 && cursorstate == 2 { // r was pressed so replace next char in ascii mode
            match key {
                c @ 32...126 => { buf[cursorpos] = c },
                _ => (),
            }
            mode = 0;
        } else
        if mode == 1 {
            let mask = if cursorstate == 0 { 0x0F } else { 0xF0 };
            let shift = if cursorstate == 0 { 4 } else { 0 };
            match key { // Change the selected nibble
                c @ 65... 70 => // A-F
                    { buf[cursorpos] = buf[cursorpos]&mask | (c-55)<<shift },
                c @ 97...102 => // a-f
                    { buf[cursorpos] = buf[cursorpos]&mask | (c-87)<<shift },
                c @ 48... 57 => // 0-9
                    { buf[cursorpos] = buf[cursorpos]&mask | (c-48)<<shift },
                _ => ()
            }
            mode = 0;
        } else
        if mode == 2 {
            match key {
                c @ 32...126 => { command.push(c as char); },
                27 => {command.clear();mode = 0;},
                10 => { // Enter pressed, compute command!
                        if (command == "w".to_string()) || (command == "wq".to_string()) {
                            file.seek(SeekFrom::Start(0)).ok().expect("Filepointer could not be set to 0");
                            file.write_all(&mut buf).ok().expect("File could not be written.");
                            if command == "wq".to_string() {
                                quitnow = 1;
                            }
                            command.clear();mode = 0;
                        }
                        else if command == "q".to_string() {
                            quitnow = 1;
                        }
                        else {
                            command.clear();
                            command.push_str("Bad_command!");
                            mode = 0;
                        }
                    },
                _ => (),
            }
        } else
        if mode == 3 {
            if cursorstate == 0 { // Left nibble
                match key {
                    c @ 65... 70 => // A-F
                        {buf.insert(cursorpos, (c-55)<<4 ); cursorstate = 1;},
                    c @ 97...102 => // a-f
                        {buf.insert(cursorpos, (c-87)<<4 ); cursorstate = 1;},
                    c @ 48... 57 => // 0-9
                        {buf.insert(cursorpos, (c-48)<<4 ); cursorstate = 1;},
                    27 => {mode = 0;},
                    _ => ()
                }
            } else
            if cursorstate == 1 { // Right nibble
                match key {
                    c @ 65...70 => // A-F
                        { buf[cursorpos] = buf[cursorpos]&0xF0 | (c-55); cursorstate = 0; cursorpos+=1; },
                    c @ 97...102 => // a-f
                        { buf[cursorpos] = buf[cursorpos]&0xF0 | (c-87); cursorstate = 0; cursorpos+=1; },
                    c @ 48...57 => // 0-9
                        { buf[cursorpos] = buf[cursorpos]&0xF0 | (c-48); cursorstate = 0; cursorpos+=1; },
                    27 => {mode = 0;},
                    _ => ()
                }
            } else
            if cursorstate == 2 { // Ascii
                match key {
                    c @ 32...126 => { buf.insert(cursorpos, c); cursorpos+=1; },
                    27 => {mode = 0;},
                    _ => (),
                }
            }
        }
        draw(&buf, cursorpos, SPALTEN, screenheight, mode, &command, cursorstate, screenoffset);
    }

    refresh();
    endwin();
}

fn draw(buf:&Vec<u8>, cursorpos:usize, spalten:usize, maxzeilen:usize, mode:usize, command:&String, cursorstate:usize, screenoffset:usize) {
    erase();

    let mut zeilen = buf.len() / spalten;
    if zeilen >= maxzeilen-1 { // Last line reserved for Status/Commands/etc (Like in vim)
        zeilen = maxzeilen-2;
    }

    for z in 0 .. zeilen+1 {
        printw(&format!("{:08X}: ", z*spalten+screenoffset*spalten )); // 8 hex digits (4GB/spalten or 0.25GB@spalten=SPALTEN)
        printw(" "); // Additional space between line number and hex
        for s in 0 .. spalten {
            if z*spalten+screenoffset*spalten+s < buf.len() {
                if z*spalten+screenoffset*spalten+s == cursorpos { // Color of left nibble
                    if cursorstate == 0 {attron(COLOR_PAIR(1) | A_STANDOUT());}
                    else if cursorstate == 2 {attron(A_UNDERLINE());}
                }
                printw(&format!("{:01X}", buf[z*spalten+screenoffset*spalten+s]>>4) ); // Display left nibble
                if z*spalten+screenoffset*spalten+s == cursorpos { // End of color left nibble
                    if cursorstate == 0 {attroff(COLOR_PAIR(1) | A_STANDOUT());}
                    else if cursorstate == 2 {attroff(A_UNDERLINE());}
                }

                if z*spalten+screenoffset*spalten+s == cursorpos { // Color of right nibble
                    if cursorstate == 1 {attron(COLOR_PAIR(1) | A_STANDOUT());}
                    else if cursorstate == 2 {attron(A_UNDERLINE());}
                }
                printw(&format!("{:01X}", buf[z*spalten+screenoffset*spalten+s]&0x0F) ); // Display right nibble
                if z*spalten+screenoffset*spalten+s == cursorpos {
                    if cursorstate == 1 {attroff(COLOR_PAIR(1) | A_STANDOUT());}
                    else if cursorstate == 2 {attroff(A_UNDERLINE());}
                }
                printw(" ");
            } else {
                printw("-- ");
            }
        }
        printw(" "); // Additional space between hex and ascii
        for s in 0 .. spalten {
            if z*spalten+screenoffset*spalten+s == cursorpos {
                if cursorstate == 2 {attron(COLOR_PAIR(1) | A_STANDOUT());}
                else {attron(A_UNDERLINE());}
            }
            if z*spalten+screenoffset*spalten+s < buf.len() {
                if let c @ 32...126 = buf[z*spalten+screenoffset*spalten+s] {
                    if c as char == '%' {
                        printw("%%"); // '%' needs to be escaped by a '%' in ncurses
                    } else {
                        printw(&format!("{}", c as char) );
                    }
                }
                else {printw(&format!(".") );}
            }
            if z*spalten+screenoffset*spalten+s == cursorpos {
                if cursorstate == 2 {attroff(COLOR_PAIR(1) | A_STANDOUT());}
                else {attroff(A_UNDERLINE());}
            }
        }
        printw("\n");
    }
    for _ in 0 .. maxzeilen-zeilen-2 {
        printw("\n"); // Put the cursor on last line of terminal
    }
    if mode == 2 {
        printw(":"); // Indicate that a command can be typed in
    }
    if mode == 3 {
        printw("insert"); // Indicate that insert mode is active
    }
    printw(&format!("{}", command));
}
<|endoftext|>


<commit_before>use std::io::{self, BufRead};

const ADDITION: &str = "\x1b[32m"; // Green
const REMOVAL: &str = "\x1b[31m";  // Red
const NORMAL: &str = "\x1b[0m";

fn print_adds_and_removes(adds: &Vec<String>, removes: &Vec<String>) {
    for remove_line in removes {
        println!("{}{}", REMOVAL, remove_line)
    }

    for add_line in adds {
        println!("{}{}", ADDITION, add_line)
    }

    print!("{}", NORMAL);
}

fn main() {
    println!("Now reading from stdin and printing to stdout:");

    let stdin = io::stdin();
    let mut adds: Vec<String> = Vec::new();
    let mut removes: Vec<String> = Vec::new();
    for line in stdin.lock().lines() {
        let line = line.unwrap();
        if !line.is_empty() && line.chars().next().unwrap() == '+' {
            adds.push(line);
            continue;
        } else if !line.is_empty() && line.chars().next().unwrap() == '-' {
            removes.push(line);
            continue;
        }

        // Print diff section
        print_adds_and_removes(&adds, &removes);
        adds.clear();
        removes.clear();

        // Print current line
        println!("{}", line);
    }
    print_adds_and_removes(&adds, &removes);

    print!("{}", NORMAL);
}
<commit_msg>Slices, not vectors<commit_after>use std::io::{self, BufRead};

const ADDITION: &str = "\x1b[32m"; // Green
const REMOVAL: &str = "\x1b[31m";  // Red
const NORMAL: &str = "\x1b[0m";

fn print_adds_and_removes(adds: &[String], removes: &[String]) {
    for remove_line in removes {
        println!("{}{}", REMOVAL, remove_line)
    }

    for add_line in adds {
        println!("{}{}", ADDITION, add_line)
    }

    print!("{}", NORMAL);
}

fn main() {
    println!("Now reading from stdin and printing to stdout:");

    let stdin = io::stdin();
    let mut adds: Vec<String> = Vec::new();
    let mut removes: Vec<String> = Vec::new();
    for line in stdin.lock().lines() {
        let line = line.unwrap();
        if !line.is_empty() && line.chars().next().unwrap() == '+' {
            adds.push(line);
            continue;
        } else if !line.is_empty() && line.chars().next().unwrap() == '-' {
            removes.push(line);
            continue;
        }

        // Print diff section
        print_adds_and_removes(&adds, &removes);
        adds.clear();
        removes.clear();

        // Print current line
        println!("{}", line);
    }
    print_adds_and_removes(&adds, &removes);

    print!("{}", NORMAL);
}
<|endoftext|>