<commit_before>#![feature(alloc_system)]
#![feature(io)]
extern crate alloc_system;
extern crate libc;
extern crate ncurses;
extern crate getopts;
extern crate regex;
#[macro_use] extern crate lazy_static;
mod item;
mod reader;
mod input;
mod matcher;
mod event;
mod model;
mod score;
mod orderedvec;
mod curses;
mod query;
mod ansi;
mod sender;

use std::thread;
use std::time::Duration;
use getopts::Options;
use std::env;
use std::sync::mpsc::{sync_channel, channel, Sender, Receiver};
use event::Event::*;

use std::io::Write;
macro_rules! println_stderr(
    ($($arg:tt)*) => { {
        let r = writeln!(&mut ::std::io::stderr(), $($arg)*);
        r.expect("failed printing to stderr");
    } }
);

fn main() {
    let exit_code = real_main();
    std::process::exit(exit_code);
}

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [options]", program);
    print!("{}", opts.usage(&brief));
}

fn real_main() -> i32 {

    //------------------------------------------------------------------------------
    // parse options
    let mut opts = Options::new();
    opts.optopt("b", "bind", "comma seperated keybindings, such as 'ctrl-j:accept,ctrl-k:kill-line'", "KEY:ACTION");
    opts.optflag("h", "help", "print this help menu");
    opts.optflag("m", "multi", "Enable Multiple Selection");
    opts.optflag("", "no-multi", "Disable Multiple Selection");
    opts.optopt("p", "prompt", "prompt string", "'> '");
    opts.optopt("", "cmd-prompt", "prompt string", "'> '");
    opts.optopt("e", "expect", "comma seperated keys that can be used to complete skim", "KEYS");
    opts.optopt("t", "tiebreak", "comma seperated criteria", "[score,index,begin,end,-score,...]");
    opts.optflag("", "ansi", "parse ANSI color codes for input strings");
    opts.optopt("c", "cmd", "command to invoke dynamically", "ag");
    opts.optflag("i", "interactive", "Use skim as an interactive interface");
    opts.optopt("q", "query", "specify the initial query", "\"\"");
    opts.optopt("d", "delimiter", "specify the delimiter(in REGEX) for fields", "\\t");
    opts.optopt("n", "nth", "specify the fields to be matched", "1,2..5");
    opts.optopt("", "with-nth", "specify the fields to be transformed", "1,2..5");
    opts.optopt("I", "", "replace `replstr` with the selected item", "replstr");
    opts.optflag("", "version", "print out the current version of skim");

    let mut args = Vec::new();

    let program = env::args().nth(0).unwrap_or("sk".to_string());
    for arg in env::args().skip(1) {
        args.push(arg);
    }
    let options = match opts.parse(args) {
        Ok(m) => { m }
        Err(f) => { panic!(f.to_string()) }
    };

    // print help message
    if options.opt_present("h") {
        print_usage(&program, opts);
        return 0;
    }

    // print version
    if options.opt_present("version") {
        println!("0.1.2");
        return 0;
    }

    //------------------------------------------------------------------------------
    // query
    let default_command = match env::var("SKIM_DEFAULT_COMMAND") {
        Ok(val) => val,
        Err(_) => "find .".to_string(),
    };
    let mut query = query::Query::builder()
        .cmd(&default_command)
        .build();
    query.parse_options(&options);

    //------------------------------------------------------------------------------
    // reader
    let (tx_reader, rx_reader) = channel();
    let (tx_item, rx_item) = sync_channel(1024);
    let mut reader = reader::Reader::new(rx_reader, tx_item);
    reader.parse_options(&options);
    thread::spawn(move || {
        reader.run();
    });

    //------------------------------------------------------------------------------
    // matcher
    let (tx_model, rx_model) = channel();
    let tx_model_clone = tx_model.clone();
    let mut matcher = matcher::Matcher::new(rx_item, tx_model_clone);
    matcher.parse_options(&options);

    thread::spawn(move || {
        matcher.run();
    });

    //------------------------------------------------------------------------------
    // model
    let mut model = model::Model::new(rx_model);
    model.parse_options(&options);
    thread::spawn(move || {
        model.run();
    });

    //------------------------------------------------------------------------------
    // input
    let (tx_input, rx_input) = channel();
    let tx_input_clone = tx_input.clone();
    let mut input = input::Input::new(tx_input_clone);
    input.parse_keymap(options.opt_str("b"));
    input.parse_expect_keys(options.opt_str("e"));
    thread::spawn(move || {
        input.run();
    });

    //------------------------------------------------------------------------------
    // start a timer for notifying refresh
    let tx_input_clone = tx_input.clone();
    let (tx_view, rx_view) = channel();
    thread::spawn(move || {
        loop {
            let timeout = rx_view.recv_timeout(Duration::from_millis(150));
            if timeout.is_ok() {
                // some urgent refresh is needed
                let _ = tx_input_clone.send((EvActRedraw, Box::new(true)));

                // to prevent from bounds, remove sequent urgent refresh
                thread::sleep(Duration::from_millis(50));
                while let Ok(_) = rx_view.try_recv() {}
            } else {
                let _ = tx_input_clone.send((EvActRedraw, Box::new(true)));
            }
        }
    });

    //------------------------------------------------------------------------------
    // Helper functions

    // light up the fire
    let _ = tx_reader.send((EvReaderRestart, Box::new((query.get_cmd(), query.get_query()))));

    let on_query_change = |query: &query::Query| {
        // restart the reader with new parameter
        let _ = tx_reader.send((EvReaderRestart, Box::new((query.get_cmd(), query.get_query()))));
        // send redraw event
        let _ = tx_view.send(true);
    };

    let redraw_screen = || { let _ = tx_view.send(true); };

    //------------------------------------------------------------------------------
    // main loop, listen for user input
    // now we can use
    // tx_reader: send message to reader
    // tx_model:  send message to model
    // rx_input:  receive keystroke events

    let mut exit_code = 1;
    while let Ok((ev, arg)) = rx_input.recv() {
        match ev {
            EvActAddChar =>  {
                let ch: char = *arg.downcast().unwrap();
                query.act_add_char(ch);
                on_query_change(&query);
            }

            EvActBackwardDeleteChar => {
                query.act_backward_delete_char();
                on_query_change(&query);
            }

            EvActDeleteCharEOF | EvActDeleteChar => {
                query.act_delete_char();
                on_query_change(&query);
            }

            EvActBackwardChar => {
                query.act_backward_char();
                redraw_screen();
            }

            EvActForwardChar => {
                query.act_forward_char();
                redraw_screen();
            }

            EvActBackwardKillWord | EvActUnixWordRubout => {
                query.act_backward_kill_word();
                on_query_change(&query);
            }

            EvActBackwardWord => {
                query.act_backward_word();
                redraw_screen();
            }

            EvActForwardWord => {
                query.act_forward_word();
                redraw_screen();
            }

            EvActBeginningOfLine => {
                query.act_beginning_of_line();
                redraw_screen();
            }

            EvActEndOfLine => {
                query.act_end_of_line();
                redraw_screen();
            }

            EvActKillLine => {
                query.act_kill_line();
                on_query_change(&query);
            }

            EvActUnixLineDiscard => {
                query.act_line_discard();
                on_query_change(&query);
            }

            EvActKillWord => {
                query.act_kill_word();
                on_query_change(&query);
            }

            EvActRotateMode => {
                query.act_query_rotate_mode();
                redraw_screen();
            }

            EvActAccept => {
                // sync with model to quit

                let accept_key = *arg.downcast::<Option<String>>()
                    .unwrap_or(Box::new(None));

                let (tx, rx): (Sender<usize>, Receiver<usize>) = channel();
                let _ = tx_model.send((EvActAccept, Box::new((accept_key, tx))));
                let selected = rx.recv().unwrap_or(0);;
                exit_code = if selected > 0 {0} else {1};
                break;
            }

            EvActClearScreen | EvActRedraw => {
                let _ = tx_model.send((EvModelRedraw, Box::new(query.get_print_func())));
            }

            EvActAbort => {
                exit_code = 130;
                break;
            }

            EvActUp | EvActDown
                | EvActToggle | EvActToggleDown | EvActToggleUp
                | EvActToggleAll | EvActSelectAll | EvActDeselectAll
                | EvActPageDown | EvActPageUp
                | EvActScrollLeft | EvActScrollRight => {
                let _ = tx_model.send((ev, arg));
                redraw_screen();
            }

            _ => {}
        }
    }

    exit_code
}
<commit_msg>[view] refresh faster on key press;<commit_after>#![feature(alloc_system)]
#![feature(io)]
extern crate alloc_system;
extern crate libc;
extern crate ncurses;
extern crate getopts;
extern crate regex;
#[macro_use] extern crate lazy_static;
mod item;
mod reader;
mod input;
mod matcher;
mod event;
mod model;
mod score;
mod orderedvec;
mod curses;
mod query;
mod ansi;
mod sender;

use std::thread;
use std::time::Duration;
use getopts::Options;
use std::env;
use std::sync::mpsc::{sync_channel, channel, Sender, Receiver};
use event::Event::*;

use std::io::Write;
macro_rules! println_stderr(
    ($($arg:tt)*) => { {
        let r = writeln!(&mut ::std::io::stderr(), $($arg)*);
        r.expect("failed printing to stderr");
    } }
);

fn main() {
    let exit_code = real_main();
    std::process::exit(exit_code);
}

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [options]", program);
    print!("{}", opts.usage(&brief));
}

fn real_main() -> i32 {

    //------------------------------------------------------------------------------
    // parse options
    let mut opts = Options::new();
    opts.optopt("b", "bind", "comma seperated keybindings, such as 'ctrl-j:accept,ctrl-k:kill-line'", "KEY:ACTION");
    opts.optflag("h", "help", "print this help menu");
    opts.optflag("m", "multi", "Enable Multiple Selection");
    opts.optflag("", "no-multi", "Disable Multiple Selection");
    opts.optopt("p", "prompt", "prompt string", "'> '");
    opts.optopt("", "cmd-prompt", "prompt string", "'> '");
    opts.optopt("e", "expect", "comma seperated keys that can be used to complete skim", "KEYS");
    opts.optopt("t", "tiebreak", "comma seperated criteria", "[score,index,begin,end,-score,...]");
    opts.optflag("", "ansi", "parse ANSI color codes for input strings");
    opts.optopt("c", "cmd", "command to invoke dynamically", "ag");
    opts.optflag("i", "interactive", "Use skim as an interactive interface");
    opts.optopt("q", "query", "specify the initial query", "\"\"");
    opts.optopt("d", "delimiter", "specify the delimiter(in REGEX) for fields", "\\t");
    opts.optopt("n", "nth", "specify the fields to be matched", "1,2..5");
    opts.optopt("", "with-nth", "specify the fields to be transformed", "1,2..5");
    opts.optopt("I", "", "replace `replstr` with the selected item", "replstr");
    opts.optflag("", "version", "print out the current version of skim");

    let mut args = Vec::new();

    let program = env::args().nth(0).unwrap_or("sk".to_string());
    for arg in env::args().skip(1) {
        args.push(arg);
    }
    let options = match opts.parse(args) {
        Ok(m) => { m }
        Err(f) => { panic!(f.to_string()) }
    };

    // print help message
    if options.opt_present("h") {
        print_usage(&program, opts);
        return 0;
    }

    // print version
    if options.opt_present("version") {
        println!("0.1.2");
        return 0;
    }

    //------------------------------------------------------------------------------
    // query
    let default_command = match env::var("SKIM_DEFAULT_COMMAND") {
        Ok(val) => val,
        Err(_) => "find .".to_string(),
    };
    let mut query = query::Query::builder()
        .cmd(&default_command)
        .build();
    query.parse_options(&options);

    //------------------------------------------------------------------------------
    // reader
    let (tx_reader, rx_reader) = channel();
    let (tx_item, rx_item) = sync_channel(1024);
    let mut reader = reader::Reader::new(rx_reader, tx_item);
    reader.parse_options(&options);
    thread::spawn(move || {
        reader.run();
    });

    //------------------------------------------------------------------------------
    // matcher
    let (tx_model, rx_model) = channel();
    let tx_model_clone = tx_model.clone();
    let mut matcher = matcher::Matcher::new(rx_item, tx_model_clone);
    matcher.parse_options(&options);

    thread::spawn(move || {
        matcher.run();
    });

    //------------------------------------------------------------------------------
    // model
    let mut model = model::Model::new(rx_model);
    model.parse_options(&options);
    thread::spawn(move || {
        model.run();
    });

    //------------------------------------------------------------------------------
    // input
    let (tx_input, rx_input) = channel();
    let tx_input_clone = tx_input.clone();
    let mut input = input::Input::new(tx_input_clone);
    input.parse_keymap(options.opt_str("b"));
    input.parse_expect_keys(options.opt_str("e"));
    thread::spawn(move || {
        input.run();
    });

    //------------------------------------------------------------------------------
    // start a timer for notifying refresh
    let tx_input_clone = tx_input.clone();
    let (tx_view, rx_view) = channel();
    thread::spawn(move || {
        loop {
            let timeout = rx_view.recv_timeout(Duration::from_millis(150));
            if timeout.is_ok() {
                // to prevent from bounds, remove sequent urgent refresh
                thread::sleep(Duration::from_millis(50));
                while let Ok(_) = rx_view.try_recv() {}

                // some urgent refresh is needed
                let _ = tx_input_clone.send((EvActRedraw, Box::new(true)));
            } else {
                let _ = tx_input_clone.send((EvActRedraw, Box::new(true)));
            }
        }
    });

    //------------------------------------------------------------------------------
    // Helper functions

    // light up the fire
    let _ = tx_reader.send((EvReaderRestart, Box::new((query.get_cmd(), query.get_query()))));

    let on_query_change = |query: &query::Query| {
        // restart the reader with new parameter
        let _ = tx_reader.send((EvReaderRestart, Box::new((query.get_cmd(), query.get_query()))));
        // send redraw event
        let _ = tx_view.send(true);
    };

    let redraw_screen = || { let _ = tx_view.send(true); };

    //------------------------------------------------------------------------------
    // main loop, listen for user input
    // now we can use
    // tx_reader: send message to reader
    // tx_model:  send message to model
    // rx_input:  receive keystroke events

    let mut exit_code = 1;
    while let Ok((ev, arg)) = rx_input.recv() {
        match ev {
            EvActAddChar =>  {
                let ch: char = *arg.downcast().unwrap();
                query.act_add_char(ch);
                on_query_change(&query);
            }

            EvActBackwardDeleteChar => {
                query.act_backward_delete_char();
                on_query_change(&query);
            }

            EvActDeleteCharEOF | EvActDeleteChar => {
                query.act_delete_char();
                on_query_change(&query);
            }

            EvActBackwardChar => {
                query.act_backward_char();
                redraw_screen();
            }

            EvActForwardChar => {
                query.act_forward_char();
                redraw_screen();
            }

            EvActBackwardKillWord | EvActUnixWordRubout => {
                query.act_backward_kill_word();
                on_query_change(&query);
            }

            EvActBackwardWord => {
                query.act_backward_word();
                redraw_screen();
            }

            EvActForwardWord => {
                query.act_forward_word();
                redraw_screen();
            }

            EvActBeginningOfLine => {
                query.act_beginning_of_line();
                redraw_screen();
            }

            EvActEndOfLine => {
                query.act_end_of_line();
                redraw_screen();
            }

            EvActKillLine => {
                query.act_kill_line();
                on_query_change(&query);
            }

            EvActUnixLineDiscard => {
                query.act_line_discard();
                on_query_change(&query);
            }

            EvActKillWord => {
                query.act_kill_word();
                on_query_change(&query);
            }

            EvActRotateMode => {
                query.act_query_rotate_mode();
                redraw_screen();
            }

            EvActAccept => {
                // sync with model to quit

                let accept_key = *arg.downcast::<Option<String>>()
                    .unwrap_or(Box::new(None));

                let (tx, rx): (Sender<usize>, Receiver<usize>) = channel();
                let _ = tx_model.send((EvActAccept, Box::new((accept_key, tx))));
                let selected = rx.recv().unwrap_or(0);;
                exit_code = if selected > 0 {0} else {1};
                break;
            }

            EvActClearScreen | EvActRedraw => {
                let _ = tx_model.send((EvModelRedraw, Box::new(query.get_print_func())));
            }

            EvActAbort => {
                exit_code = 130;
                break;
            }

            EvActUp | EvActDown
                | EvActToggle | EvActToggleDown | EvActToggleUp
                | EvActToggleAll | EvActSelectAll | EvActDeselectAll
                | EvActPageDown | EvActPageUp
                | EvActScrollLeft | EvActScrollRight => {
                let _ = tx_model.send((ev, arg));
                redraw_screen();
            }

            _ => {}
        }
    }

    exit_code
}
<|endoftext|>


<commit_before>#![feature(scoped)]

extern crate rucache;
use rucache::memcache;

extern crate getopts;
extern crate time;
extern crate byteorder;
use byteorder::{BigEndian, ByteOrder};

#[macro_use]
extern crate log;
use log::{LogRecord, LogLevel, LogLevelFilter, LogMetadata};

use std::io::Write;
use std::net;
use std::thread;
use std::env;
use std::str::FromStr;
use std::process;
use std::sync;
use std::sync::mpsc;
use std::io;
use std::str;

extern crate num;
use num::traits::FromPrimitive;

fn from_mctime(i : u32) -> i64 {
    if i > 0 && i < 30*24*60*60 {
        time::get_time().sec + i as i64
    } else {
        i as i64
    }
}

fn main() {
    let _ = log::set_logger(|max_log_level| {
        max_log_level.set(LogLevelFilter::Warn);
        Box::new(SimpleLogger)
    });

    let mut opts = getopts::Options::new();
    opts.optflagopt("p", "", "Listen on TCP port <num>, the default is port 11211.", "<num>");
    opts.optflagopt("U", "", "Listen on UDP port <num>, the default is port 11211, 0 is off.", "<num>");
    opts.optflag("h", "help", "display this help and exit");

    let args: Vec<String> = env::args().collect();
    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => {
            println!("{}", f);
            process::exit(1);
        }
    };

    if matches.opt_present("help") {
        let brief = format!("Usage: {} [options]", args[0]);
        println!("{}", opts.usage(&brief));
        return;
    }

    let host = "127.0.0.1";
    let mut tcp_port = 11211;
    if let Some(port) = matches.opt_str("p") {
        if let Ok(portn) = u16::from_str(&port) {
            tcp_port = portn;
        }
    }

    info!("listening on {}:tcp:{}", host, tcp_port);
    let listener = net::TcpListener::bind((host, tcp_port)).unwrap();

    info!("allocating map");
    let m = rucache::new(1 << 10);

    info!("spinning up worker pool");
    let (tx, rx) = mpsc::channel();
    let rxmx = sync::Mutex::new(rx);
    let pool : Vec<thread::JoinGuard<()>> = (1..1000).map(|_| {
        let map = &m;
        let rxl = &rxmx;
        thread::scoped(move || {
            handle_clients(map, rxl);
        })
    }).collect();

    info!("accepting connections");
    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                if let Err(e) = tx.send(stream) {
                    panic!("failed to queue incoming request {}", e);
                }
            }
            Err(e) => {
                warn!("{}", e);
            }
        }
    }

    drop(pool);
}

fn read_full(r : &mut io::Read, to : &mut [u8]) -> Result<(), io::Error> {
    let mut nread = 0 as usize;
    while nread < to.len() {
        match r.read(&mut to[nread..]) {
            Ok(n) if n == 0 => { return Err(std::io::Error::new(std::io::ErrorKind::ConnectionAborted, "")); }
            Ok(n) => { nread += n; }
            Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {},
            Err(e) => return Err(From::from(e))
        }
    }
    Ok(())
}

fn execute(m : &rucache::Map, req : &memcache::Request, c : &mut net::TcpStream) -> bool {
    let mut rh = memcache::ResponseHeader::from_req(req);
    match memcache::Command::from_u8(req.op) {
        Some(op) => {
            let key = req.key();
            let extras = req.extras();
            let body = req.body();

            match op.noq() {
                memcache::Command::FLUSH if extras.len() == 4 => {
                    let mut tm = from_mctime(BigEndian::read_u32(extras));
                    if tm == 0 {
                        tm = time::get_time().sec;
                    }
                    m.touchall(tm);

                    rh.status = memcache::Status::SUCCESS as u16;
                    rh.construct(&[], &[], &[]).transmit(c)
                }
                memcache::Command::SET
                | memcache::Command::ADD
                | memcache::Command::REPLACE
                if extras.len() == 8 => {
                    let flags = BigEndian::read_u32(&extras[0..4]);
                    let expires = from_mctime(BigEndian::read_u32(&extras[4..8]));

                    // XXX: unfortunate copy...
                    let b = body.to_vec();

                    let res = match op.noq() {
                        memcache::Command::SET if req.cas == 0 => m.set(key, b, flags, expires),
                        memcache::Command::REPLACE if req.cas == 0 => m.replace(key, b, flags, expires),
                        memcache::Command::ADD => m.add(key, b, flags, expires),
                        memcache::Command::SET | memcache::Command::REPLACE | _ => {
                            m.cas(key, b, flags, expires, req.cas)
                        }
                    };

                    rh.status = res.0 as u16;
                    if let (memcache::Status::SUCCESS, Ok(v)) = res {
                        rh.cas = v.val.casid;
                    }

                    rh.construct(&[], &[], &[]).transmit(c)
                }
                memcache::Command::DELETE => {
                    rh.status = m.delete(key, req.cas).0 as u16;
                    rh.construct(&[], &[], &[]).transmit(c)
                }
                memcache::Command::INCREMENT
                | memcache::Command::DECREMENT
                if extras.len() == 20 => {
                    let by = BigEndian::read_u64(&extras[0..8]);
                    let def = BigEndian::read_u64(&extras[8..16]);
                    let expires = from_mctime(BigEndian::read_u32(&extras[16..20]));

                    let v = match op.noq() {
                        memcache::Command::INCREMENT => m.incr(key, by, def, expires),
                        memcache::Command::DECREMENT | _ => m.decr(key, by, def, expires),
                    };

                    rh.status = v.0 as u16;
                    if let (memcache::Status::SUCCESS, Ok(v)) = v {
                        let extras = &mut [0u8; 8];
                        BigEndian::write_u64(&mut extras[..], u64::from_str(str::from_utf8(&v.val.bytes[..]).unwrap()).unwrap());

                        rh.cas = v.val.casid;
                        rh.construct(&extras[..], &[], &[]).transmit(c)
                    } else {
                        rh.construct(&[], &[], &[]).transmit(c)
                    }
                }
                memcache::Command::GET | memcache::Command::GETK => {
                    let v = m.get(key);
                    rh.status = v.0 as u16;

                    if let (memcache::Status::SUCCESS, Ok(v)) = v {
                        rh.cas = v.val.casid;
                        let extras = &mut [0u8; 4];
                        BigEndian::write_u32(&mut extras[..], v.val.flags);
                        rh.construct(
                            &extras[..],
                            if op.noq() == memcache::Command::GETK {key} else {&[]},
                            &v.val.bytes[..]
                        ).transmit(c)
                    } else {
                        rh.construct(&[], &[], &[]).transmit(c)
                    }
                }
                memcache::Command::APPEND | memcache::Command::PREPEND => {
                    // XXX: unfortunate copy...
                    let b = body.to_vec();

                    let res = match op.noq() {
                        memcache::Command::PREPEND => m.prepend(key, b, req.cas),
                        memcache::Command::APPEND | _ => m.append(key, b, req.cas),
                    };

                    rh.status = res.0 as u16;
                    if let (memcache::Status::SUCCESS, Ok(v)) = res {
                        rh.cas = v.val.casid;
                    }

                    rh.construct(&[], &[], &[]).transmit(c)
                }
                memcache::Command::VERSION => {
                    rh.status = memcache::Status::SUCCESS as u16;
                    rh.construct(&[], &[], "0.1.0".as_bytes()).transmit(c)
                }
                memcache::Command::NOOP => {
                    rh.status = memcache::Status::SUCCESS as u16;
                    rh.construct(&[], &[], &[]).transmit(c)
                }
                _ => {
                    warn!("client sent unhandled command: {:?}", op);
                    rh.status = memcache::Status::EINVAL as u16;
                    rh.construct(&[], &[], &[]).transmit(c)
                }
            }
        }
        None => {
            rh.status = memcache::Status::UNKNOWN_COMMAND as u16;
            rh.construct(&[], &[], &[]).transmit(c)
        }
    }
}

fn handle_client(m : &rucache::Map, mut c : net::TcpStream) {
    let mut magic = [0_u8; 1];
    let mut body = Vec::with_capacity(100);
    'outer: loop {
        if let Err(_) = read_full(&mut c, &mut magic) {
            break 'outer;
        }

        unsafe { body.set_len(100); }
        body.shrink_to_fit();
        body.clear();
        unsafe { body.set_len(24); }
        match magic[0] {
            0x80 => {
                // binary protocol
                // memcache request
                body[0] = magic[0];
                if let Err(_) = read_full(&mut c, &mut body[1..24]) {
                    break 'outer;
                }

                let blen = BigEndian::read_u32(&body[8..12]) as usize;
                body.reserve_exact(blen);
                unsafe { body.set_len(24 + blen); }

                if let Err(_) = read_full(&mut c, &mut body[24..(24+blen)]) {
                    break 'outer;
                }

                let req = memcache::Request::parse(&mut body[..]);
                if !execute(m, req, &mut c) {
                    break 'outer;
                }
            }
            0x81 => {
                warn!("expected request from client, got response magic");
                break 'outer;
            }
            _ => {
                // text protocol
            }
        }
    };

    info!("client {:?} hung up", c);
}

fn handle_clients(m : &rucache::Map, rxmx : &sync::Mutex<mpsc::Receiver<net::TcpStream>>) {
    loop {
        if let Ok(rx) = rxmx.lock() {
            if let Ok(c) = rx.recv() {
                drop(rx);
                handle_client(&m, c);
            } else {
                // channel receive error
                return;
            }
        } else {
            // lock poisoned by panic
            return;
        }
    }
}

struct SimpleLogger;
impl log::Log for SimpleLogger {
    fn enabled(&self, metadata: &LogMetadata) -> bool {
        metadata.level() <= LogLevel::Warn
    }

    #[allow(unused_must_use)]
    fn log(&self, record: &LogRecord) {
        if self.enabled(record.metadata()) {
            writeln!(&mut std::io::stderr(), "{} - {}", record.level(), record.args());
        }
    }
}

<commit_msg>Handle binding errors<commit_after>#![feature(scoped)]

extern crate rucache;
use rucache::memcache;

extern crate getopts;
extern crate time;
extern crate byteorder;
use byteorder::{BigEndian, ByteOrder};

#[macro_use]
extern crate log;
use log::{LogRecord, LogLevel, LogLevelFilter, LogMetadata};

use std::io::Write;
use std::net;
use std::thread;
use std::env;
use std::str::FromStr;
use std::process;
use std::sync;
use std::sync::mpsc;
use std::io;
use std::str;

extern crate num;
use num::traits::FromPrimitive;

fn from_mctime(i : u32) -> i64 {
    if i > 0 && i < 30*24*60*60 {
        time::get_time().sec + i as i64
    } else {
        i as i64
    }
}

fn main() {
    let _ = log::set_logger(|max_log_level| {
        max_log_level.set(LogLevelFilter::Warn);
        Box::new(SimpleLogger)
    });

    let mut opts = getopts::Options::new();
    opts.optflagopt("p", "", "Listen on TCP port <num>, the default is port 11211.", "<num>");
    opts.optflagopt("U", "", "Listen on UDP port <num>, the default is port 11211, 0 is off.", "<num>");
    opts.optflag("h", "help", "display this help and exit");

    let args: Vec<String> = env::args().collect();
    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => {
            println!("{}", f);
            process::exit(1);
        }
    };

    if matches.opt_present("help") {
        let brief = format!("Usage: {} [options]", args[0]);
        println!("{}", opts.usage(&brief));
        return;
    }

    let host = "127.0.0.1";
    let mut tcp_port = 11211;
    if let Some(port) = matches.opt_str("p") {
        if let Ok(portn) = u16::from_str(&port) {
            tcp_port = portn;
        }
    }

    info!("listening on {}:tcp:{}", host, tcp_port);
    let listener = net::TcpListener::bind((host, tcp_port));
    if let Err(e) = listener {
        panic!("failed to listen on port: {}", e);
    }

    info!("allocating map");
    let m = rucache::new(1 << 10);

    info!("spinning up worker pool");
    let (tx, rx) = mpsc::channel();
    let rxmx = sync::Mutex::new(rx);
    let pool : Vec<thread::JoinGuard<()>> = (1..1000).map(|_| {
        let map = &m;
        let rxl = &rxmx;
        thread::scoped(move || {
            handle_clients(map, rxl);
        })
    }).collect();

    info!("accepting connections");
    for stream in listener.unwrap().incoming() {
        match stream {
            Ok(stream) => {
                if let Err(e) = tx.send(stream) {
                    panic!("failed to queue incoming request {}", e);
                }
            }
            Err(e) => {
                warn!("{}", e);
            }
        }
    }

    drop(pool);
}

fn read_full(r : &mut io::Read, to : &mut [u8]) -> Result<(), io::Error> {
    let mut nread = 0 as usize;
    while nread < to.len() {
        match r.read(&mut to[nread..]) {
            Ok(n) if n == 0 => { return Err(std::io::Error::new(std::io::ErrorKind::ConnectionAborted, "")); }
            Ok(n) => { nread += n; }
            Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {},
            Err(e) => return Err(From::from(e))
        }
    }
    Ok(())
}

fn execute(m : &rucache::Map, req : &memcache::Request, c : &mut net::TcpStream) -> bool {
    let mut rh = memcache::ResponseHeader::from_req(req);
    match memcache::Command::from_u8(req.op) {
        Some(op) => {
            let key = req.key();
            let extras = req.extras();
            let body = req.body();

            match op.noq() {
                memcache::Command::FLUSH if extras.len() == 4 => {
                    let mut tm = from_mctime(BigEndian::read_u32(extras));
                    if tm == 0 {
                        tm = time::get_time().sec;
                    }
                    m.touchall(tm);

                    rh.status = memcache::Status::SUCCESS as u16;
                    rh.construct(&[], &[], &[]).transmit(c)
                }
                memcache::Command::SET
                | memcache::Command::ADD
                | memcache::Command::REPLACE
                if extras.len() == 8 => {
                    let flags = BigEndian::read_u32(&extras[0..4]);
                    let expires = from_mctime(BigEndian::read_u32(&extras[4..8]));

                    // XXX: unfortunate copy...
                    let b = body.to_vec();

                    let res = match op.noq() {
                        memcache::Command::SET if req.cas == 0 => m.set(key, b, flags, expires),
                        memcache::Command::REPLACE if req.cas == 0 => m.replace(key, b, flags, expires),
                        memcache::Command::ADD => m.add(key, b, flags, expires),
                        memcache::Command::SET | memcache::Command::REPLACE | _ => {
                            m.cas(key, b, flags, expires, req.cas)
                        }
                    };

                    rh.status = res.0 as u16;
                    if let (memcache::Status::SUCCESS, Ok(v)) = res {
                        rh.cas = v.val.casid;
                    }

                    rh.construct(&[], &[], &[]).transmit(c)
                }
                memcache::Command::DELETE => {
                    rh.status = m.delete(key, req.cas).0 as u16;
                    rh.construct(&[], &[], &[]).transmit(c)
                }
                memcache::Command::INCREMENT
                | memcache::Command::DECREMENT
                if extras.len() == 20 => {
                    let by = BigEndian::read_u64(&extras[0..8]);
                    let def = BigEndian::read_u64(&extras[8..16]);
                    let expires = from_mctime(BigEndian::read_u32(&extras[16..20]));

                    let v = match op.noq() {
                        memcache::Command::INCREMENT => m.incr(key, by, def, expires),
                        memcache::Command::DECREMENT | _ => m.decr(key, by, def, expires),
                    };

                    rh.status = v.0 as u16;
                    if let (memcache::Status::SUCCESS, Ok(v)) = v {
                        let extras = &mut [0u8; 8];
                        BigEndian::write_u64(&mut extras[..], u64::from_str(str::from_utf8(&v.val.bytes[..]).unwrap()).unwrap());

                        rh.cas = v.val.casid;
                        rh.construct(&extras[..], &[], &[]).transmit(c)
                    } else {
                        rh.construct(&[], &[], &[]).transmit(c)
                    }
                }
                memcache::Command::GET | memcache::Command::GETK => {
                    let v = m.get(key);
                    rh.status = v.0 as u16;

                    if let (memcache::Status::SUCCESS, Ok(v)) = v {
                        rh.cas = v.val.casid;
                        let extras = &mut [0u8; 4];
                        BigEndian::write_u32(&mut extras[..], v.val.flags);
                        rh.construct(
                            &extras[..],
                            if op.noq() == memcache::Command::GETK {key} else {&[]},
                            &v.val.bytes[..]
                        ).transmit(c)
                    } else {
                        rh.construct(&[], &[], &[]).transmit(c)
                    }
                }
                memcache::Command::APPEND | memcache::Command::PREPEND => {
                    // XXX: unfortunate copy...
                    let b = body.to_vec();

                    let res = match op.noq() {
                        memcache::Command::PREPEND => m.prepend(key, b, req.cas),
                        memcache::Command::APPEND | _ => m.append(key, b, req.cas),
                    };

                    rh.status = res.0 as u16;
                    if let (memcache::Status::SUCCESS, Ok(v)) = res {
                        rh.cas = v.val.casid;
                    }

                    rh.construct(&[], &[], &[]).transmit(c)
                }
                memcache::Command::VERSION => {
                    rh.status = memcache::Status::SUCCESS as u16;
                    rh.construct(&[], &[], "0.1.0".as_bytes()).transmit(c)
                }
                memcache::Command::NOOP => {
                    rh.status = memcache::Status::SUCCESS as u16;
                    rh.construct(&[], &[], &[]).transmit(c)
                }
                _ => {
                    warn!("client sent unhandled command: {:?}", op);
                    rh.status = memcache::Status::EINVAL as u16;
                    rh.construct(&[], &[], &[]).transmit(c)
                }
            }
        }
        None => {
            rh.status = memcache::Status::UNKNOWN_COMMAND as u16;
            rh.construct(&[], &[], &[]).transmit(c)
        }
    }
}

fn handle_client(m : &rucache::Map, mut c : net::TcpStream) {
    let mut magic = [0_u8; 1];
    let mut body = Vec::with_capacity(100);
    'outer: loop {
        if let Err(_) = read_full(&mut c, &mut magic) {
            break 'outer;
        }

        unsafe { body.set_len(100); }
        body.shrink_to_fit();
        body.clear();
        unsafe { body.set_len(24); }
        match magic[0] {
            0x80 => {
                // binary protocol
                // memcache request
                body[0] = magic[0];
                if let Err(_) = read_full(&mut c, &mut body[1..24]) {
                    break 'outer;
                }

                let blen = BigEndian::read_u32(&body[8..12]) as usize;
                body.reserve_exact(blen);
                unsafe { body.set_len(24 + blen); }

                if let Err(_) = read_full(&mut c, &mut body[24..(24+blen)]) {
                    break 'outer;
                }

                let req = memcache::Request::parse(&mut body[..]);
                if !execute(m, req, &mut c) {
                    break 'outer;
                }
            }
            0x81 => {
                warn!("expected request from client, got response magic");
                break 'outer;
            }
            _ => {
                // text protocol
            }
        }
    };

    info!("client {:?} hung up", c);
}

fn handle_clients(m : &rucache::Map, rxmx : &sync::Mutex<mpsc::Receiver<net::TcpStream>>) {
    loop {
        if let Ok(rx) = rxmx.lock() {
            if let Ok(c) = rx.recv() {
                drop(rx);
                handle_client(&m, c);
            } else {
                // channel receive error
                return;
            }
        } else {
            // lock poisoned by panic
            return;
        }
    }
}

struct SimpleLogger;
impl log::Log for SimpleLogger {
    fn enabled(&self, metadata: &LogMetadata) -> bool {
        metadata.level() <= LogLevel::Warn
    }

    #[allow(unused_must_use)]
    fn log(&self, record: &LogRecord) {
        if self.enabled(record.metadata()) {
            writeln!(&mut std::io::stderr(), "{} - {}", record.level(), record.args());
        }
    }
}

<|endoftext|>


<commit_before>#[macro_use]
extern crate lazy_static;

use diffus::{
    edit::{self, string},
    Diffable,
};
use regex::Regex;
use std::io::{self, BufRead};

const ADD: &str = "\x1b[32m"; // Green
const REMOVE: &str = "\x1b[31m"; // Red
const HUNK_HEADER: &str = "\x1b[36m"; // Cyan

const INVERSE_VIDEO: &str = "\x1b[7m";
const NOT_INVERSE_VIDEO: &str = "\x1b[27m";

const BOLD: &str = "\x1b[1m";

const NORMAL: &str = "\x1b[0m";

lazy_static! {
    static ref STATIC_HEADERS: Vec<(Regex, &'static str)> = vec![
        (Regex::new("^diff ").unwrap(), BOLD),
        (Regex::new("^index ").unwrap(), BOLD),
        (Regex::new("^--- ").unwrap(), BOLD),
        (Regex::new("^\\+\\+\\+ ").unwrap(), BOLD),
        (Regex::new("^@@ ").unwrap(), HUNK_HEADER),
    ];
}

/// Format add and remove lines in ADD and REMOVE colors.
///
/// No intra-line refinement.
#[must_use]
fn simple_format_adds_and_removes(adds: &[String], removes: &[String]) -> Vec<String> {
    let mut lines: Vec<String> = Vec::new();

    for remove_line in removes {
        lines.push(format!("{}{}", REMOVE, remove_line));
    }

    for add_line in adds {
        // The NORMAL at the end is required for the following lines not to be printed
        // with ADD coloring.
        lines.push(format!("{}{}{}", ADD, add_line, NORMAL))
    }

    return lines;
}

/// Joins multiple lines into a single string.
///
/// The first character of each line is skipped, because it is assumed to be
/// either a `+` or a `-` in a diff.
///
/// Between each joined line a `\n` linefeed character is inserted.
fn join_skip_first(lines: &[String]) -> String {
    let mut joined = String::new();
    for line in lines {
        if !line.is_empty() {
            joined.push_str("\n")
        }
        joined.push_str(&line[1..]);
    }

    return joined;
}

fn print_adds_and_removes(adds: &[String], removes: &[String]) {
    if adds.is_empty() {
        for line in simple_format_adds_and_removes(adds, removes) {
            println!("{}", line);
        }
        return;
    }

    if removes.is_empty() {
        for line in simple_format_adds_and_removes(adds, removes) {
            println!("{}", line);
        }
        return;
    }

    // Join inputs by linefeeds into strings
    let adds = join_skip_first(adds);
    let removes = join_skip_first(removes);

    // Find diffs between adds and removals
    let mut highlighted_adds = String::new();
    let mut highlighted_removes = String::new();
    let mut adds_is_inverse = false;
    let mut removes_is_inverse = false;
    let diff = removes.diff(&adds);
    match diff {
        edit::Edit::Copy(unchanged) => {
            highlighted_adds.push_str(unchanged);
            highlighted_removes.push_str(unchanged);
        }
        edit::Edit::Change(diff) => {
            diff.into_iter()
                .map(|edit| {
                    match edit {
                        string::Edit::Copy(elem) => {
                            if adds_is_inverse {
                                highlighted_adds.push_str(NOT_INVERSE_VIDEO);
                            }
                            adds_is_inverse = false;

                            if removes_is_inverse {
                                highlighted_removes.push_str(NOT_INVERSE_VIDEO);
                            }
                            removes_is_inverse = false;

                            highlighted_adds.push(elem);
                            highlighted_removes.push(elem);
                        }
                        string::Edit::Insert(elem) => {
                            if !adds_is_inverse {
                                highlighted_adds.push_str(INVERSE_VIDEO);
                            }
                            adds_is_inverse = true;

                            highlighted_adds.push(elem);
                        }
                        string::Edit::Remove(elem) => {
                            if !removes_is_inverse {
                                highlighted_removes.push_str(INVERSE_VIDEO);
                            }
                            removes_is_inverse = true;

                            highlighted_removes.push(elem);
                        }
                    };
                })
                .for_each(drop);
        }
    }

    for highlighted_remove in highlighted_removes.lines() {
        println!("{}-{}", REMOVE, highlighted_remove);
    }
    for highlighted_add in highlighted_adds.lines() {
        println!("{}+{}", ADD, highlighted_add);
    }
}

fn get_fixed_highlight(line: &str) -> &str {
    for static_header in STATIC_HEADERS.iter() {
        let re = &static_header.0;
        if re.is_match(line) {
            return static_header.1;
        }
    }

    return "";
}

fn main() {
    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();

        let fixed_highlight = get_fixed_highlight(&line);
        if !fixed_highlight.is_empty() {
            // Drain outstanding adds / removes
            print_adds_and_removes(&adds, &removes);
            adds.clear();
            removes.clear();

            println!("{}{}{}", fixed_highlight, line, NORMAL);
            continue;
        }

        // Collect adds / removes
        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;
        }

        // Drain outstanding adds / removes
        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>Make diff formatting testable<commit_after>#[macro_use]
extern crate lazy_static;

use diffus::{
    edit::{self, string},
    Diffable,
};
use regex::Regex;
use std::io::{self, BufRead};

const ADD: &str = "\x1b[32m"; // Green
const REMOVE: &str = "\x1b[31m"; // Red
const HUNK_HEADER: &str = "\x1b[36m"; // Cyan

const INVERSE_VIDEO: &str = "\x1b[7m";
const NOT_INVERSE_VIDEO: &str = "\x1b[27m";

const BOLD: &str = "\x1b[1m";

const NORMAL: &str = "\x1b[0m";

lazy_static! {
    static ref STATIC_HEADERS: Vec<(Regex, &'static str)> = vec![
        (Regex::new("^diff ").unwrap(), BOLD),
        (Regex::new("^index ").unwrap(), BOLD),
        (Regex::new("^--- ").unwrap(), BOLD),
        (Regex::new("^\\+\\+\\+ ").unwrap(), BOLD),
        (Regex::new("^@@ ").unwrap(), HUNK_HEADER),
    ];
}

/// Format add and remove lines in ADD and REMOVE colors.
///
/// No intra-line refinement.
#[must_use]
fn simple_format_adds_and_removes(adds: &[String], removes: &[String]) -> Vec<String> {
    let mut lines: Vec<String> = Vec::new();

    for remove_line in removes {
        lines.push(format!("{}{}", REMOVE, remove_line));
    }

    for add_line in adds {
        // The NORMAL at the end is required for the following lines not to be printed
        // with ADD coloring.
        lines.push(format!("{}{}{}", ADD, add_line, NORMAL))
    }

    return lines;
}

/// Joins multiple lines into a single string.
///
/// The first character of each line is skipped, because it is assumed to be
/// either a `+` or a `-` in a diff.
///
/// Between each joined line a `\n` linefeed character is inserted.
fn join_skip_first(lines: &[String]) -> String {
    let mut joined = String::new();
    for line in lines {
        if !line.is_empty() {
            joined.push_str("\n")
        }
        joined.push_str(&line[1..]);
    }

    return joined;
}

#[must_use]
fn format_adds_and_removes(adds: &[String], removes: &[String]) -> Vec<String> {
    if adds.is_empty() {
        return simple_format_adds_and_removes(adds, removes);
    }

    if removes.is_empty() {
        return simple_format_adds_and_removes(adds, removes);
    }

    // Join inputs by linefeeds into strings
    let adds = join_skip_first(adds);
    let removes = join_skip_first(removes);

    // Find diffs between adds and removals
    let mut highlighted_adds = String::new();
    let mut highlighted_removes = String::new();
    let mut adds_is_inverse = false;
    let mut removes_is_inverse = false;
    let diff = removes.diff(&adds);
    match diff {
        edit::Edit::Copy(unchanged) => {
            highlighted_adds.push_str(unchanged);
            highlighted_removes.push_str(unchanged);
        }
        edit::Edit::Change(diff) => {
            diff.into_iter()
                .map(|edit| {
                    match edit {
                        string::Edit::Copy(elem) => {
                            if adds_is_inverse {
                                highlighted_adds.push_str(NOT_INVERSE_VIDEO);
                            }
                            adds_is_inverse = false;

                            if removes_is_inverse {
                                highlighted_removes.push_str(NOT_INVERSE_VIDEO);
                            }
                            removes_is_inverse = false;

                            highlighted_adds.push(elem);
                            highlighted_removes.push(elem);
                        }
                        string::Edit::Insert(elem) => {
                            if !adds_is_inverse {
                                highlighted_adds.push_str(INVERSE_VIDEO);
                            }
                            adds_is_inverse = true;

                            highlighted_adds.push(elem);
                        }
                        string::Edit::Remove(elem) => {
                            if !removes_is_inverse {
                                highlighted_removes.push_str(INVERSE_VIDEO);
                            }
                            removes_is_inverse = true;

                            highlighted_removes.push(elem);
                        }
                    };
                })
                .for_each(drop);
        }
    }

    let mut lines: Vec<String> = Vec::new();
    for highlighted_remove in highlighted_removes.lines() {
        lines.push(format!("{}-{}", REMOVE, highlighted_remove));
    }
    for highlighted_add in highlighted_adds.lines() {
        lines.push(format!("{}+{}", ADD, highlighted_add));
    }

    return lines;
}

fn get_fixed_highlight(line: &str) -> &str {
    for static_header in STATIC_HEADERS.iter() {
        let re = &static_header.0;
        if re.is_match(line) {
            return static_header.1;
        }
    }

    return "";
}

fn main() {
    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();

        let fixed_highlight = get_fixed_highlight(&line);
        if !fixed_highlight.is_empty() {
            // Drain outstanding adds / removes
            for line in format_adds_and_removes(&adds, &removes) {
                println!("{}", line);
            }
            adds.clear();
            removes.clear();

            println!("{}{}{}", fixed_highlight, line, NORMAL);
            continue;
        }

        // Collect adds / removes
        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;
        }

        // Drain outstanding adds / removes
        for line in format_adds_and_removes(&adds, &removes) {
            println!("{}", line);
        }
        adds.clear();
        removes.clear();

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

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


<commit_before>#![cfg_attr(feature = "dev", allow(unstable_features))]
#![cfg_attr(feature = "dev", feature(plugin))]
#![cfg_attr(feature = "dev", plugin(clippy))]

mod logger;
mod toml_file;
mod git;
mod changelog;
mod commit_analyzer;
mod cargo;
mod error;
mod github;
mod config;
mod utils;

extern crate rustc_serialize;
extern crate toml;
extern crate regex;
extern crate semver;
extern crate docopt;
extern crate git2;
extern crate clog;
extern crate hyper;
extern crate hubcaps;
extern crate url;
extern crate travis_after_all;
extern crate env_logger;

use docopt::Docopt;
use commit_analyzer::CommitType;
use config::ConfigBuilder;
use std::process;
use semver::Version;
use std::{env,fs};
use std::path::Path;
use std::error::Error;
use std::thread;
use std::time::Duration;
use travis_after_all::Build;
use utils::user_repo_from_url;

const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const USERAGENT: &'static str = concat!("semantic-rs/", env!("CARGO_PKG_VERSION"));
const USAGE: &'static str = "
semantic.rs 🚀

Usage:
  semantic-rs [options]
  semantic-rs --version

Options:
  -h --help              Show this screen.
  --version              Show version.
  -p PATH, --path=PATH   Specifies the repository path. [default: .]
  -w W, --write=W        Write changes to files (default: yes if CI is set, otherwise no).
  -r R, --release=R      Create release on GitHub and publish on crates.io (only in write mode) [default: yes].
  -b B, --branch=B       The branch on which releases should happen. [default: master].
";

const COMMITTER_ERROR_MESSAGE: &'static str = r"
A release commit needs a committer name and email address.
We tried fetching it from different locations, but couldn't find one.

Committer information is taken from the following environment variables, if set:

GIT_COMMITTER_NAME
GIT_COMMITTER_EMAIL

If none is set the normal git config is tried in the following order:

Local repository config
User config
Global config";

macro_rules! print_exit {
    ($fmt:expr) => {{
        logger::stderr($fmt);
        process::exit(1);
    }};
    ($fmt:expr, $($arg:tt)*) => {{
        logger::stderr(format!($fmt, $($arg)*));
        process::exit(1);
    }};
}

#[derive(Debug, RustcDecodable)]
struct Args {
    flag_path: String,
    flag_write: Option<String>,
    flag_version: bool,
    flag_release: String,
    flag_branch: String,
}

fn string_to_bool(answer: &str) -> bool {
    match &answer.to_lowercase()[..] {
        "yes" | "true" | "1" => true,
        _ => false
    }
}

fn version_bump(version: &Version, bump: CommitType) -> Option<Version> {
    let mut version = version.clone();
    match bump {
        CommitType::Unknown => return None,
        CommitType::Patch => version.increment_patch(),
        CommitType::Minor => version.increment_minor(),
        CommitType::Major => version.increment_major(),
    }

    Some(version)
}

fn ci_env_set() -> bool {
    env::var("CI").is_ok()
}

fn current_branch(repo: &git2::Repository) -> Option<String> {
    if let Ok(branch) = env::var("TRAVIS_BRANCH") {
        return Some(branch)
    }

    let head = repo.head().expect("No HEAD found for repository");

    if head.is_branch() {
        let short = head.shorthand().expect("No branch name found");
        return Some(short.into());
    }

    None
}

fn is_release_branch(current: &str, release: &str) -> bool {
    if let Ok(pr) = env::var("TRAVIS_PULL_REQUEST") {
        if pr != "false" {
            return false;
        }
    }

    current == release
}

fn push_to_github(config: &config::Config, tag_name: &str) {
    logger::stdout("Pushing new commit and tag");
    git::push(&config, &tag_name)
        .unwrap_or_else(|err| print_exit!("Failed to push git: {:?}", err));

    logger::stdout("Waiting a tiny bit, so GitHub can store the git tag");
    thread::sleep(Duration::from_secs(1));
}

fn release_on_github(config: &config::Config, tag_message: &str, tag_name: &str) {
    if github::can_release(&config) {
        logger::stdout("Creating GitHub release");
        github::release(&config, &tag_name, &tag_message)
            .unwrap_or_else(|err| print_exit!("Failed to create GitHub release: {:?}", err));
    } else {
        logger::stdout("Project not hosted on GitHub. Skipping release step");
    }
}

fn release_on_cratesio(config: &config::Config) {
    logger::stdout("Publishing crate on crates.io");
    if !cargo::publish(&config.repository_path, &config.cargo_token.as_ref().unwrap()) {
        print_exit!("Failed to publish on crates.io");
    }
}

fn generate_changelog(repository_path: &str, version: &Version, new_version: &String) -> String {
    logger::stdout(format!("New version would be: {}", new_version));
    logger::stdout("Would write the following Changelog:");
    match changelog::generate(repository_path, &version.to_string(), new_version) {
        Ok(log) => log,
        Err(err) => {
            logger::stderr(format!("Generating Changelog failed: {:?}", err));
            process::exit(1)
        }
    }
}

fn write_changelog(repository_path: &str, version: &Version, new_version: &str) {
    logger::stdout("Writing Changelog");
    changelog::write(repository_path, &version.to_string(), &new_version)
        .unwrap_or_else(|err| print_exit!("Writing Changelog failed: {:?}", err));
}

fn print_changelog(changelog: &str) {
    logger::stdout("====================================");
    logger::stdout(changelog);
    logger::stdout("====================================");
    logger::stdout("Would create annotated git tag");
}

fn package_crate(config: &config::Config, repository_path: &str, new_version: &str) {
    if config.release_mode {
        logger::stdout("Updating lockfile");
        if !cargo::update_lockfile(repository_path) {
            print_exit!("`cargo fetch` failed. See above for the cargo error message.");
        }
    }

    git::commit_files(&config, &new_version)
        .unwrap_or_else(|err| print_exit!("Committing files failed: {:?}", err));

    logger::stdout("Package crate");
    if !cargo::package(repository_path) {
        print_exit!("`cargo package` failed. See above for the cargo error message.");
    }
}

fn get_repo(repository_path: &str) -> git2::Repository {
    match git2::Repository::open(repository_path) {
        Ok(repo) => repo,
        Err(e) => {
            logger::stderr(format!("Could not open the git repository: {:?}", e));
            process::exit(1);
        }
    }
}

fn get_repository_path(args: &Args) -> String {
    let path = Path::new(&args.flag_path);
    let path = fs::canonicalize(path)
        .unwrap_or_else(|_| print_exit!("Path does not exist or a component is
                                                            not a directory"));
    let repo_path = path.to_str().unwrap_or_else(|| print_exit!("Path is not valid unicode"));
    repo_path.to_string()
}

fn get_signature<'a>(repository_path: String) -> git2::Signature<'a> {
    let repo = get_repo(&repository_path);
    let signature = match git::get_signature(&repo) {
        Ok(sig) => sig,
            Err(e) => {
                logger::stderr(format!("Failed to get the committer's name and email address: {}", e.description()));
                logger::stderr(COMMITTER_ERROR_MESSAGE);
                process::exit(1);
            }
    };

    signature.to_owned()
}

fn get_user_and_repo(repository_path: &str) -> (Option<String>, Option<String>) {
    let repo = get_repo(repository_path);
    let remote_or_none = repo.find_remote("origin");
    match remote_or_none {
        Ok(remote) => {
            let url = remote.url().expect("Remote URL is not valid UTF-8").to_owned();
            let (user, repo_name) = user_repo_from_url(&url)
                .unwrap_or_else(|e| print_exit!("Could not extract user and repository name from URL: {:?}", e));

            (Some(user), Some(repo_name))
        },
        Err(err) => {
            logger::warn(format!("Could not determine the origin remote url: {:?}", err));
            logger::warn("semantic-rs can't push changes or create a release on GitHub");
            (None, None)
        }
    }
}

fn get_github_token(repository_path: &str) -> Option<String> {
    let repo = get_repo(repository_path);
    let remote_or_none = repo.find_remote("origin");
    match remote_or_none {
        Ok(remote) => {
            let url = remote.url().expect("Remote URL is not valid UTF-8").to_owned();
            if github::is_github_url(&url) {
                match env::var("GH_TOKEN") {
                    Ok(token) => Some(token),
                    Err(_) => None
                }
            } else {
                None
            }
        },
        Err(_) => None
    }
}

fn get_cargo_token() -> Option<String> {
    match env::var("CARGO_TOKEN") {
        Ok(token) => Some(token),
        Err(_) => None
    }
}

fn assemble_configuration(args: Args) -> config::Config {
    let mut config_builder = ConfigBuilder::new();

    // If write mode is requested OR denied,
    // adhere to the user's wish,
    // otherwise we decide based on whether we are running in CI.
    let write_mode = match args.flag_write {
        None => ci_env_set(),
        Some(ref flag) => string_to_bool(flag)
    };

    // We can only release, if we are allowed to write
    let release_mode = write_mode && string_to_bool(&args.flag_release);
    let repository_path = get_repository_path(&args);

    config_builder.write(write_mode);
    config_builder.release(release_mode);
    config_builder.branch(args.flag_branch.clone());
    config_builder.repository_path(repository_path.clone());
    config_builder.signature(get_signature(repository_path.clone()));
    let (user, repo) = get_user_and_repo(&repository_path);
    if user.is_some() {
        config_builder.user(user.unwrap());
    }
    if repo.is_some() {
        config_builder.repository_name(repo.unwrap());
    }
    let gh_token = get_github_token(&repository_path);
    if gh_token.is_some() {
        config_builder.gh_token(gh_token.unwrap());
    }

    let cargo_token = get_cargo_token();
    if cargo_token.is_some() {
        config_builder.cargo_token(cargo_token.unwrap());
    }
    config_builder.repository(get_repo(&repository_path));
    config_builder.build()
}

fn main() {
    env_logger::init().expect("Can't instantiate env logger");
    println!("semantic.rs 🚀");

    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    if args.flag_version {
        println!("semantic.rs 🚀 -- v{}", VERSION);
        process::exit(0);
    }

    let config = assemble_configuration(args);

    let branch = current_branch(&config.repository)
        .unwrap_or_else(|| print_exit!("Could not determine current branch."));

    if !is_release_branch(&branch, &config.branch) {
        println!("Current branch is '{}', releases are only done from branch '{}'", branch, config.branch);
        println!("No release done from a pull request either.");
        process::exit(0);
    }

    if config.release_mode && ci_env_set() {
        let build_run = Build::from_env()
            .unwrap_or_else(|e| print_exit!("CI mode, but can't check other builds. Error: {:?}", e));

        if !build_run.is_leader() {
            println!("Not the build leader. Nothing to do. Bye.");
            process::exit(0);
        }

        println!("I am the build leader. Waiting for other jobs to finish.");
        match build_run.wait_for_others() {
            Ok(()) => println!("Other jobs finished and succeeded. Doing my work now."),
                Err(travis_after_all::Error::FailedBuilds) => {
                    print_exit!("Some builds failed. Stopping here.");
                },
                Err(e) => print_exit!("Waiting for other builds failed Reason: {:?}", e),
        }
    }

    let version = toml_file::read_from_file(&config.repository_path)
        .unwrap_or_else(|err| print_exit!("Reading `Cargo.toml` failed: {:?}", err));

    let version = Version::parse(&version).expect("Not a valid version");
    logger::stdout(format!("Current version: {}", version.to_string()));

    logger::stdout("Analyzing commits");

    let bump = git::version_bump_since_latest(&config.repository);
    if config.write_mode {
        logger::stdout(format!("Commits analyzed. Bump will be {:?}", bump));
    } else {
        logger::stdout(format!("Commits analyzed. Bump would be {:?}", bump));
    }
    let new_version = match version_bump(&version, bump) {
        Some(new_version) => new_version.to_string(),
            None => {
                logger::stdout("No version bump. Nothing to do.");
                process::exit(0);
            }
    };

    if !config.write_mode {
        let changelog = generate_changelog(&config.repository_path, &version, &new_version);
        print_changelog(&changelog);
    } else {
        logger::stdout(format!("New version: {}", new_version));

        toml_file::write_new_version(&config.repository_path, &new_version)
            .unwrap_or_else(|err| print_exit!("Writing `Cargo.toml` failed: {:?}", err));

        write_changelog(&config.repository_path, &version, &new_version);
        package_crate(&config, &config.repository_path, &new_version);

        logger::stdout("Creating annotated git tag");
        let tag_message = changelog::generate(&config.repository_path, &version.to_string(), &new_version)
            .unwrap_or_else(|err| print_exit!("Can't generate changelog: {:?}", err));

        let tag_name = format!("v{}", new_version);
        git::tag(&config, &tag_name, &tag_message)
            .unwrap_or_else(|err| print_exit!("Failed to create git tag: {:?}", err));

        if config.release_mode && config.can_push() {
            push_to_github(&config, &tag_name);
            release_on_github(&config, &tag_message, &tag_name);
            release_on_cratesio(&config);
            println!("{} v{} is released. 🚀🚀🚀", config.repository_name.unwrap(), new_version);
        }
    }
}
<commit_msg>refactor: Return either user and repo together or nothing<commit_after>#![cfg_attr(feature = "dev", allow(unstable_features))]
#![cfg_attr(feature = "dev", feature(plugin))]
#![cfg_attr(feature = "dev", plugin(clippy))]

mod logger;
mod toml_file;
mod git;
mod changelog;
mod commit_analyzer;
mod cargo;
mod error;
mod github;
mod config;
mod utils;

extern crate rustc_serialize;
extern crate toml;
extern crate regex;
extern crate semver;
extern crate docopt;
extern crate git2;
extern crate clog;
extern crate hyper;
extern crate hubcaps;
extern crate url;
extern crate travis_after_all;
extern crate env_logger;

use docopt::Docopt;
use commit_analyzer::CommitType;
use config::ConfigBuilder;
use std::process;
use semver::Version;
use std::{env,fs};
use std::path::Path;
use std::error::Error;
use std::thread;
use std::time::Duration;
use travis_after_all::Build;
use utils::user_repo_from_url;

const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const USERAGENT: &'static str = concat!("semantic-rs/", env!("CARGO_PKG_VERSION"));
const USAGE: &'static str = "
semantic.rs 🚀

Usage:
  semantic-rs [options]
  semantic-rs --version

Options:
  -h --help              Show this screen.
  --version              Show version.
  -p PATH, --path=PATH   Specifies the repository path. [default: .]
  -w W, --write=W        Write changes to files (default: yes if CI is set, otherwise no).
  -r R, --release=R      Create release on GitHub and publish on crates.io (only in write mode) [default: yes].
  -b B, --branch=B       The branch on which releases should happen. [default: master].
";

const COMMITTER_ERROR_MESSAGE: &'static str = r"
A release commit needs a committer name and email address.
We tried fetching it from different locations, but couldn't find one.

Committer information is taken from the following environment variables, if set:

GIT_COMMITTER_NAME
GIT_COMMITTER_EMAIL

If none is set the normal git config is tried in the following order:

Local repository config
User config
Global config";

macro_rules! print_exit {
    ($fmt:expr) => {{
        logger::stderr($fmt);
        process::exit(1);
    }};
    ($fmt:expr, $($arg:tt)*) => {{
        logger::stderr(format!($fmt, $($arg)*));
        process::exit(1);
    }};
}

#[derive(Debug, RustcDecodable)]
struct Args {
    flag_path: String,
    flag_write: Option<String>,
    flag_version: bool,
    flag_release: String,
    flag_branch: String,
}

fn string_to_bool(answer: &str) -> bool {
    match &answer.to_lowercase()[..] {
        "yes" | "true" | "1" => true,
        _ => false
    }
}

fn version_bump(version: &Version, bump: CommitType) -> Option<Version> {
    let mut version = version.clone();
    match bump {
        CommitType::Unknown => return None,
        CommitType::Patch => version.increment_patch(),
        CommitType::Minor => version.increment_minor(),
        CommitType::Major => version.increment_major(),
    }

    Some(version)
}

fn ci_env_set() -> bool {
    env::var("CI").is_ok()
}

fn current_branch(repo: &git2::Repository) -> Option<String> {
    if let Ok(branch) = env::var("TRAVIS_BRANCH") {
        return Some(branch)
    }

    let head = repo.head().expect("No HEAD found for repository");

    if head.is_branch() {
        let short = head.shorthand().expect("No branch name found");
        return Some(short.into());
    }

    None
}

fn is_release_branch(current: &str, release: &str) -> bool {
    if let Ok(pr) = env::var("TRAVIS_PULL_REQUEST") {
        if pr != "false" {
            return false;
        }
    }

    current == release
}

fn push_to_github(config: &config::Config, tag_name: &str) {
    logger::stdout("Pushing new commit and tag");
    git::push(&config, &tag_name)
        .unwrap_or_else(|err| print_exit!("Failed to push git: {:?}", err));

    logger::stdout("Waiting a tiny bit, so GitHub can store the git tag");
    thread::sleep(Duration::from_secs(1));
}

fn release_on_github(config: &config::Config, tag_message: &str, tag_name: &str) {
    if github::can_release(&config) {
        logger::stdout("Creating GitHub release");
        github::release(&config, &tag_name, &tag_message)
            .unwrap_or_else(|err| print_exit!("Failed to create GitHub release: {:?}", err));
    } else {
        logger::stdout("Project not hosted on GitHub. Skipping release step");
    }
}

fn release_on_cratesio(config: &config::Config) {
    logger::stdout("Publishing crate on crates.io");
    if !cargo::publish(&config.repository_path, &config.cargo_token.as_ref().unwrap()) {
        print_exit!("Failed to publish on crates.io");
    }
}

fn generate_changelog(repository_path: &str, version: &Version, new_version: &String) -> String {
    logger::stdout(format!("New version would be: {}", new_version));
    logger::stdout("Would write the following Changelog:");
    match changelog::generate(repository_path, &version.to_string(), new_version) {
        Ok(log) => log,
        Err(err) => {
            logger::stderr(format!("Generating Changelog failed: {:?}", err));
            process::exit(1)
        }
    }
}

fn write_changelog(repository_path: &str, version: &Version, new_version: &str) {
    logger::stdout("Writing Changelog");
    changelog::write(repository_path, &version.to_string(), &new_version)
        .unwrap_or_else(|err| print_exit!("Writing Changelog failed: {:?}", err));
}

fn print_changelog(changelog: &str) {
    logger::stdout("====================================");
    logger::stdout(changelog);
    logger::stdout("====================================");
    logger::stdout("Would create annotated git tag");
}

fn package_crate(config: &config::Config, repository_path: &str, new_version: &str) {
    if config.release_mode {
        logger::stdout("Updating lockfile");
        if !cargo::update_lockfile(repository_path) {
            print_exit!("`cargo fetch` failed. See above for the cargo error message.");
        }
    }

    git::commit_files(&config, &new_version)
        .unwrap_or_else(|err| print_exit!("Committing files failed: {:?}", err));

    logger::stdout("Package crate");
    if !cargo::package(repository_path) {
        print_exit!("`cargo package` failed. See above for the cargo error message.");
    }
}

fn get_repo(repository_path: &str) -> git2::Repository {
    match git2::Repository::open(repository_path) {
        Ok(repo) => repo,
        Err(e) => {
            logger::stderr(format!("Could not open the git repository: {:?}", e));
            process::exit(1);
        }
    }
}

fn get_repository_path(args: &Args) -> String {
    let path = Path::new(&args.flag_path);
    let path = fs::canonicalize(path)
        .unwrap_or_else(|_| print_exit!("Path does not exist or a component is
                                                            not a directory"));
    let repo_path = path.to_str().unwrap_or_else(|| print_exit!("Path is not valid unicode"));
    repo_path.to_string()
}

fn get_signature<'a>(repository_path: String) -> git2::Signature<'a> {
    let repo = get_repo(&repository_path);
    let signature = match git::get_signature(&repo) {
        Ok(sig) => sig,
            Err(e) => {
                logger::stderr(format!("Failed to get the committer's name and email address: {}", e.description()));
                logger::stderr(COMMITTER_ERROR_MESSAGE);
                process::exit(1);
            }
    };

    signature.to_owned()
}

fn get_user_and_repo(repository_path: &str) -> Option<(String, String)> {
    let repo = get_repo(repository_path);
    let remote_or_none = repo.find_remote("origin");
    match remote_or_none {
        Ok(remote) => {
            let url = remote.url().expect("Remote URL is not valid UTF-8").to_owned();
            let (user, repo_name) = user_repo_from_url(&url)
                .unwrap_or_else(|e| print_exit!("Could not extract user and repository name from URL: {:?}", e));

            Some((user, repo_name))
        },
        Err(err) => {
            logger::warn(format!("Could not determine the origin remote url: {:?}", err));
            logger::warn("semantic-rs can't push changes or create a release on GitHub");
            None
        }
    }
}

fn get_github_token(repository_path: &str) -> Option<String> {
    let repo = get_repo(repository_path);
    let remote_or_none = repo.find_remote("origin");
    match remote_or_none {
        Ok(remote) => {
            let url = remote.url().expect("Remote URL is not valid UTF-8").to_owned();
            if github::is_github_url(&url) {
                match env::var("GH_TOKEN") {
                    Ok(token) => Some(token),
                    Err(_) => None
                }
            } else {
                None
            }
        },
        Err(_) => None
    }
}

fn get_cargo_token() -> Option<String> {
    match env::var("CARGO_TOKEN") {
        Ok(token) => Some(token),
        Err(_) => None
    }
}

fn assemble_configuration(args: Args) -> config::Config {
    let mut config_builder = ConfigBuilder::new();

    // If write mode is requested OR denied,
    // adhere to the user's wish,
    // otherwise we decide based on whether we are running in CI.
    let write_mode = match args.flag_write {
        None => ci_env_set(),
        Some(ref flag) => string_to_bool(flag)
    };

    // We can only release, if we are allowed to write
    let release_mode = write_mode && string_to_bool(&args.flag_release);
    let repository_path = get_repository_path(&args);

    config_builder.write(write_mode);
    config_builder.release(release_mode);
    config_builder.branch(args.flag_branch.clone());
    config_builder.repository_path(repository_path.clone());
    config_builder.signature(get_signature(repository_path.clone()));
    let user_and_repo = get_user_and_repo(&repository_path);
    if user_and_repo.is_some() {
        let (user, repo) = user_and_repo.unwrap();
        config_builder.user(user);
        config_builder.repository_name(repo);
    }
    let gh_token = get_github_token(&repository_path);
    if gh_token.is_some() {
        config_builder.gh_token(gh_token.unwrap());
    }

    let cargo_token = get_cargo_token();
    if cargo_token.is_some() {
        config_builder.cargo_token(cargo_token.unwrap());
    }
    config_builder.repository(get_repo(&repository_path));
    config_builder.build()
}

fn main() {
    env_logger::init().expect("Can't instantiate env logger");
    println!("semantic.rs 🚀");

    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    if args.flag_version {
        println!("semantic.rs 🚀 -- v{}", VERSION);
        process::exit(0);
    }

    let config = assemble_configuration(args);

    let branch = current_branch(&config.repository)
        .unwrap_or_else(|| print_exit!("Could not determine current branch."));

    if !is_release_branch(&branch, &config.branch) {
        println!("Current branch is '{}', releases are only done from branch '{}'", branch, config.branch);
        println!("No release done from a pull request either.");
        process::exit(0);
    }

    if config.release_mode && ci_env_set() {
        let build_run = Build::from_env()
            .unwrap_or_else(|e| print_exit!("CI mode, but can't check other builds. Error: {:?}", e));

        if !build_run.is_leader() {
            println!("Not the build leader. Nothing to do. Bye.");
            process::exit(0);
        }

        println!("I am the build leader. Waiting for other jobs to finish.");
        match build_run.wait_for_others() {
            Ok(()) => println!("Other jobs finished and succeeded. Doing my work now."),
                Err(travis_after_all::Error::FailedBuilds) => {
                    print_exit!("Some builds failed. Stopping here.");
                },
                Err(e) => print_exit!("Waiting for other builds failed Reason: {:?}", e),
        }
    }

    let version = toml_file::read_from_file(&config.repository_path)
        .unwrap_or_else(|err| print_exit!("Reading `Cargo.toml` failed: {:?}", err));

    let version = Version::parse(&version).expect("Not a valid version");
    logger::stdout(format!("Current version: {}", version.to_string()));

    logger::stdout("Analyzing commits");

    let bump = git::version_bump_since_latest(&config.repository);
    if config.write_mode {
        logger::stdout(format!("Commits analyzed. Bump will be {:?}", bump));
    } else {
        logger::stdout(format!("Commits analyzed. Bump would be {:?}", bump));
    }
    let new_version = match version_bump(&version, bump) {
        Some(new_version) => new_version.to_string(),
            None => {
                logger::stdout("No version bump. Nothing to do.");
                process::exit(0);
            }
    };

    if !config.write_mode {
        let changelog = generate_changelog(&config.repository_path, &version, &new_version);
        print_changelog(&changelog);
    } else {
        logger::stdout(format!("New version: {}", new_version));

        toml_file::write_new_version(&config.repository_path, &new_version)
            .unwrap_or_else(|err| print_exit!("Writing `Cargo.toml` failed: {:?}", err));

        write_changelog(&config.repository_path, &version, &new_version);
        package_crate(&config, &config.repository_path, &new_version);

        logger::stdout("Creating annotated git tag");
        let tag_message = changelog::generate(&config.repository_path, &version.to_string(), &new_version)
            .unwrap_or_else(|err| print_exit!("Can't generate changelog: {:?}", err));

        let tag_name = format!("v{}", new_version);
        git::tag(&config, &tag_name, &tag_message)
            .unwrap_or_else(|err| print_exit!("Failed to create git tag: {:?}", err));

        if config.release_mode && config.can_push() {
            push_to_github(&config, &tag_name);
            release_on_github(&config, &tag_message, &tag_name);
            release_on_cratesio(&config);
            println!("{} v{} is released. 🚀🚀🚀", config.repository_name.unwrap(), new_version);
        }
    }
}
<|endoftext|>


<commit_before>#![feature(globs)]
extern crate libc;

use xlib::{XOpenDisplay, XDisplayName, XQueryExtension, XInitExtension, XSynchronize, XKeysymToString, XKeycodeToKeysym};
use libc::{c_int};
use std::ptr;
use xtst::{XRecordCreateContext, XRecordAllClients, XRecordAllocRange, XRecordRange, XRecordQueryVersion, XRecordEnableContext, XRecordEnableContextAsync,XRecordInterceptData, XRecordProcessReplies, XRecordFreeData};
use std::mem;
use std::c_str::CString;
mod xtst;
mod xlib;
mod xlibint;
mod xutil;

struct XRecordDatum {
  xtype: ::libc::c_uchar,
  event: ::xlib::XEvent,
  req:   ::xlibint::xResourceReq,
  reply: ::xlibint::xGenericReply,
  error: ::xlibint::xError,
  setup: ::xlibint::xConnSetupPrefix,
}

// let mut display_control: *mut xlib::Display  = std::mem::transmute(0);
// let mut display_data: *mut xlib::Display  = std::mem::transmute(0);
// static mut display_control: *mut xlib::Display = 0 as *mut xlib::Struct__XDisplay;
// static mut display_data: *mut xlib::Display = 0 as *mut xlib::Struct__XDisplay;
static mut display_control: Display<'static> = Display {display: 0 as *mut xlib::Display};
static mut display_data: Display<'static> = Display {display: 0 as *mut xlib::Display};
static mut event_count:u32 = 0;
fn main() {
	unsafe {
		let mut a:  i8 = 0;
		display_control = Display::new();
		display_data = Display::new();

		XSynchronize(display_control.display, 1);

		let display_name = unsafe {XDisplayName(&a)};
		let ext_name = "RECORD";
		// Check presence of Record extension
		let arg2:*mut c_int = &mut 1;
		let arg3:*mut c_int = &mut 1;
		let arg4:*mut c_int = &mut 1;
		let has_record = XQueryExtension(display_control.display, ext_name.to_c_str().as_ptr() as *const i8,arg2,arg3,arg4);
		let extension = XInitExtension(display_control.display, ext_name.to_c_str().as_ptr() as *const i8);
		if extension.is_null() {
			panic!("XInitExtension() failed!");
		}

		// Get version
		let mut versionMajor: c_int = 0;
		let mut versionMinor: c_int = 0;
		XRecordQueryVersion(display_control.display, &mut versionMajor, &mut versionMinor);
		println!("RECORD extension version {}.{}", versionMajor, versionMinor);

		// Prepare record range
		let mut recordRange: XRecordRange = *XRecordAllocRange();
		let mut recordRangePtr: *mut *mut XRecordRange = std::mem::transmute(&mut &mut recordRange);
		recordRange.device_events.first = 2; // KeyPress
		recordRange.device_events.last = 6; // MotionNotify

		// Create context
		let context = XRecordCreateContext(
			display_control.display,
			0,
			&mut XRecordAllClients,
			1,
			recordRangePtr,
			1
		);
		if context == 0 {
			panic!("Fail create Record context\n");
		}

		// Run
		let res = XRecordEnableContextAsync(display_data.display, context, Some(recordCallback), &mut 0);
		if res == 0 {
			panic!("Cound not enable the Record context!\n");
		}
		xtst::XRecordFreeContext(display_data.display, context);
		loop {
			XRecordProcessReplies(display_data.display);
		}
	}
}

extern "C" fn recordCallback(pointer:*mut i8, raw_data: *mut XRecordInterceptData) {

	unsafe {

		let data = &*raw_data;
		// println!("Category {}", data.category);
		if data.category != xtst::XRecordFromServer {
			return;
		}
		println!("Event count: {}", event_count);
		event_count += 1;
		println!("Time {}", data.server_time);
		println!("Datalen {}", data.data_len);
		let mut xdatum_ptr: *mut XRecordDatum = data.data as *mut XRecordDatum;
		let mut xdatum = &*xdatum_ptr;

		let mut event = xdatum.event;
		println!("Type {}", xdatum.xtype);

		// Catch key event
		if xdatum.xtype == xtst::KeyPress || xdatum.xtype == xtst::KeyRelease {
			// extract key code
			let raw_bytes: &mut [u8,..4] = std::mem::transmute(data.data);
			let c_char  = XKeysymToString(XKeycodeToKeysym(display_control.display, raw_bytes[1], 0));
			let c_string = CString::new(std::mem::transmute(c_char), false);
			let char: &str = c_string.as_str().unwrap();
			println!("Keycode: {}, Character {}", raw_bytes[1], char);

			// println!("nitems {}", (&*wm_name).nitems);
		}
		// get current focus window
		// let current_window: *mut xlib::Window = &mut 0;
		// let revert_to_return: *mut i32 = &mut 0;
		let mut current_window = display_control.get_input_focus();
		let mut parent_window: Option<Window> = None;
		// println!("revertToReturn {}", *revert_to_return);
		// let mut j = 0u;
		// let mut wm_name: *mut xutil::XTextProperty = std::mem::transmute(&mut j);
		// let mut wm_name_true: &str = "";
		let mut wm_name_str: Option<String> = None;
		// let mut tmp =0i8;
		// let mut c_wm_name: CString = CString::new(&tmp,false);
		// let mut res = 0;
		// res = xutil::XGetWMName(display_control.display, *current_window, wm_name);
		let mut i = 0u;
		while i < 2 {
			// print!(".");
			// println!("current_window {}", current_window.id);
			if current_window.id == 0 {
				break;
			}
			// res = xutil::XGetWMName(display_control.display, *current_window, wm_name);
			wm_name_str = current_window.get_wm_name();
			match wm_name_str {
				None => {
					// println!("Move to parent");
					// let mut root: xlib::Window = 0;
					// let mut parent: xlib::Window = 0;
					// let mut childrens: *mut xlib::Window = &mut 0u64;
					// let mut nchildren: u32 = 0;
					// let r2 = xlib::XQueryTree(display_control.display, *current_window, &mut root, &mut parent, &mut childrens, &mut nchildren);
					// if r2 == 0 {
					// 	print!("*");
					// } else {
					// 	println!("parent {}", parent);
					// 	*current_window = parent;
					// }
					let tree = current_window.get_tree();
					parent_window = match tree {
						Some(WindowTree{parent: parent, children: _}) => {
							Some(parent)
						},
						_ => None
					}

				},
				Some(ref wmname) => {
					break;
				}
			}

			current_window = match parent_window {
				Some(win) => win,
				_ => current_window
			};

			// if res == 0 {
			// 	// println!("Move to parent");
			// 	// let mut root: xlib::Window = 0;
			// 	// let mut parent: xlib::Window = 0;
			// 	// let mut childrens: *mut xlib::Window = &mut 0u64;
			// 	// let mut nchildren: u32 = 0;
			// 	// let r2 = xlib::XQueryTree(display_control.display, *current_window, &mut root, &mut parent, &mut childrens, &mut nchildren);
			// 	// if r2 == 0 {
			// 	// 	print!("*");
			// 	// } else {
			// 	// 	println!("parent {}", parent);
			// 	// 	*current_window = parent;
			// 	// }
			// }
			// get parent window
			i += 1;
		}
		match wm_name_str {
			Some(ref name) => {
				println!("WM_NAME: {}", *name);
			},
			None => {
				println!("WM_NAME: none");
			}
		}
		// println!("---");
		// if res == 0 {
		// 	println!("no wmname");
		// } else {
		// 	// extract name from XTextProperty
		// 	println!("wmname found!");
		// 	println!("format: {}", (*wm_name).format);
		// 	if (*wm_name).format != 8 {
		// 		panic!("");
		// 	}
		// 	println!("encoding: {}", (*wm_name).encoding);
		// 	let encoding_name = xlib::XGetAtomName(display_control.display, (*wm_name).encoding);
		// 	let encoding_name_string = CString::new(std::mem::transmute(encoding_name), false);
		// 	println!("encoding name: {}", encoding_name_string.as_str().unwrap());
		// 	let mut tmp2 = 0i8;
		// 	let mut list: *mut *mut ::libc::c_char = std::mem::transmute(&mut &mut tmp2);
		// 	let mut list_count: i32 = 0;
		// 	let res3 = xutil::XmbTextPropertyToTextList(display_control.display, std::mem::transmute(wm_name), &mut list, &mut list_count);
		// 	println!("convert result: {}", res3);
		// 	if res3 == 0 {
		// 		println!("list count {}", list_count);
		// 		c_wm_name = CString::new(std::mem::transmute(*list), false);
		// 		wm_name_true = c_wm_name.as_str().unwrap();
		// 		println!("wm_name: {}", wm_name_true);
		// 		xlib::XFreeStringList(list);
		// 	} else {
		// 		c_wm_name = CString::new(std::mem::transmute((*wm_name).value), false);
		// 		// wm_name_true = c_wm_name.as_str().unwrap();
		// 		println!("wm_name bytes: {}", c_wm_name.as_bytes());
		// 		// ая -      27 45 76 208 239 32 27 37 71 226 128 162 27 37 64 32 45 32 83 117 98 108 105 109 101 32 84 101 120 116 32 40 85 78 82 69 71 73 83 84 69 82 69 68 41 0
		// 		// бв -      27 45 76 209 210 32 27 37 71 226 128 162 27 37 64 32 45 32 83 117 98 108 105 109 101 32 84 101 120 116 32 40 85 78 82 69 71 73 83 84 69 82 69 68 41 0
		// 		// zx -      122 120 32 27 37 71 226 128 162 27 37 64 32 45 32 83 117 98 108 105 109 101 32 84 101 120 116 32 40 85 78 82 69 71 73 83 84 69 82 69 68 41 0
		// 		// длодлод - 27 45 76 212 219 222 212 219 222 212 32 27 37 71 226 128 162 27 37 64 32 45 32 83 117 98 108 105 109 101 32 84 101 120 116 32 40 85 78 82 69 71 73 83 84 69 82 69 68 41 0
		// 		// zx • - Sublime Text (UNREGISTERED)

		// 		println!("wm_name: {}", c_wm_name);
		// 		println!("wm_name len w/o null: {}", c_wm_name.len());
		// 	}

		// }

		XRecordFreeData(raw_data);
	}
	println!("\n");
}

// ============================================================================
// Simple naive wrappers around X stuff
// ============================================================================
struct Display<'a> {
    display: *mut xlib::Display,
}

impl<'a> Display<'a> {
	fn new() -> Display<'a> {
		Display {display: unsafe {
			let mut a:  i8 = 0;
			let dpy = XOpenDisplay(&a);
			if dpy.is_null() {
				panic!("XOpenDisplay() failed!");
			}
			dpy
		}}
	}
	fn get_input_focus(&self) -> Window{
		let current_window: *mut xlib::Window = &mut 0;
		let revert_to_return: *mut i32 = &mut 0;
		unsafe{xlib::XGetInputFocus(self.display, current_window, revert_to_return)};
		Window {id: 0, display: self}
	}
}

struct Window<'a> {
    id: u64, // XID
    display: &'a Display<'a>
}

impl<'a> Window<'a> {
	fn get_wm_name(&self) -> Option<String> {
		let mut a:String = String::new();
		let wmname = unsafe {
			let mut window_name: *mut i8 = 0 as *mut i8;
			let res = xlib::XFetchName(self.display.display, self.id, &mut window_name);
			if res != 0 {
				let c_wm_name = CString::new(std::mem::transmute(window_name), false);
				// xlib::XFree(&mut window_name);
				Some(String::from_str(c_wm_name.as_str().unwrap()))

			} else {
				None
			}
		};
		wmname
	}
	fn get_tree (&self) -> Option<WindowTree> {
		unsafe {
			let mut root: xlib::Window = 0;
			let mut parent: xlib::Window = 0;
			let mut children: *mut xlib::Window = &mut 0u64;
			let mut nchildren: u32 = 0;

			let res = xlib::XQueryTree(
				self.display.display,
				self.id,
				&mut root,
				&mut parent,
				&mut children,
				&mut nchildren);

			match res {
				0 => None,
				_ => {
					let childs = match nchildren {
						0 => None,
						_ => {
							// let c = std::c_vec::CVec::new(children, nchildren);
							let mut b: Vec<Window> = Vec::new();
							for i in range(0, nchildren as int){
								b.push(Window{
									id: *children.offset(i),
									display: self.display
								});
							}
							Some(b)
						}
					};

					Some(WindowTree {
						parent: Window{
							id: parent,
							display: self.display,
						},
						children: childs
					})
				}
			}
		}
	}
}

struct WindowTree<'a> {
    parent: Window<'a>,
    children: Option<Vec<Window<'a>>>,
}<commit_msg>refactor<commit_after>#![feature(globs)]
extern crate libc;

use xlib::{XOpenDisplay, XDisplayName, XQueryExtension, XInitExtension, XSynchronize, XKeysymToString, XKeycodeToKeysym};
use libc::{c_int};
use std::ptr;
use xtst::{XRecordCreateContext, XRecordAllClients, XRecordAllocRange, XRecordRange, XRecordQueryVersion, XRecordEnableContext, XRecordEnableContextAsync,XRecordInterceptData, XRecordProcessReplies, XRecordFreeData};
use std::mem;
use std::c_str::CString;
mod xtst;
mod xlib;
mod xlibint;
mod xutil;

struct XRecordDatum {
  xtype: ::libc::c_uchar,
  event: ::xlib::XEvent,
  req:   ::xlibint::xResourceReq,
  reply: ::xlibint::xGenericReply,
  error: ::xlibint::xError,
  setup: ::xlibint::xConnSetupPrefix,
}

// let mut display_control: *mut xlib::Display  = std::mem::transmute(0);
// let mut display_data: *mut xlib::Display  = std::mem::transmute(0);
// static mut display_control: *mut xlib::Display = 0 as *mut xlib::Struct__XDisplay;
// static mut display_data: *mut xlib::Display = 0 as *mut xlib::Struct__XDisplay;
static mut display_control: Display<'static> = Display {display: 0 as *mut xlib::Display};
static mut display_data: Display<'static> = Display {display: 0 as *mut xlib::Display};
static mut event_count:u32 = 0;
fn main() {
	unsafe {
		let mut a:  i8 = 0;
		display_control = Display::new();
		display_data = Display::new();

		XSynchronize(display_control.display, 1);

		let display_name = unsafe {XDisplayName(&a)};
		let ext_name = "RECORD";
		// Check presence of Record extension
		let arg2:*mut c_int = &mut 1;
		let arg3:*mut c_int = &mut 1;
		let arg4:*mut c_int = &mut 1;
		let has_record = XQueryExtension(display_control.display, ext_name.to_c_str().as_ptr() as *const i8,arg2,arg3,arg4);
		let extension = XInitExtension(display_control.display, ext_name.to_c_str().as_ptr() as *const i8);
		if extension.is_null() {
			panic!("XInitExtension() failed!");
		}

		// Get version
		let mut versionMajor: c_int = 0;
		let mut versionMinor: c_int = 0;
		XRecordQueryVersion(display_control.display, &mut versionMajor, &mut versionMinor);
		println!("RECORD extension version {}.{}", versionMajor, versionMinor);

		// Prepare record range
		let mut recordRange: XRecordRange = *XRecordAllocRange();
		let mut recordRangePtr: *mut *mut XRecordRange = std::mem::transmute(&mut &mut recordRange);
		recordRange.device_events.first = 2; // KeyPress
		recordRange.device_events.last = 6; // MotionNotify

		// Create context
		let context = XRecordCreateContext(
			display_control.display,
			0,
			&mut XRecordAllClients,
			1,
			recordRangePtr,
			1
		);
		if context == 0 {
			panic!("Fail create Record context\n");
		}

		// Run
		let res = XRecordEnableContextAsync(display_data.display, context, Some(recordCallback), &mut 0);
		if res == 0 {
			panic!("Cound not enable the Record context!\n");
		}
		xtst::XRecordFreeContext(display_data.display, context);
		loop {
			XRecordProcessReplies(display_data.display);
		}
	}
}

extern "C" fn recordCallback(pointer:*mut i8, raw_data: *mut XRecordInterceptData) {

	unsafe {

		let data = &*raw_data;
		// println!("Category {}", data.category);
		if data.category != xtst::XRecordFromServer {
			return;
		}
		println!("Event count: {}", event_count);
		event_count += 1;
		println!("Time {}", data.server_time);
		println!("Datalen {}", data.data_len);
		let mut xdatum_ptr: *mut XRecordDatum = data.data as *mut XRecordDatum;
		let mut xdatum = &*xdatum_ptr;

		let mut event = xdatum.event;
		println!("Type {}", xdatum.xtype);

		// Catch key event
		if xdatum.xtype == xtst::KeyPress || xdatum.xtype == xtst::KeyRelease {
			// extract key code
			let raw_bytes: &mut [u8,..4] = std::mem::transmute(data.data);
			let c_char  = XKeysymToString(XKeycodeToKeysym(display_control.display, raw_bytes[1], 0));
			let c_string = CString::new(std::mem::transmute(c_char), false);
			let char: &str = c_string.as_str().unwrap();
			println!("Keycode: {}, Character {}", raw_bytes[1], char);
		}

		let mut current_window = display_control.get_input_focus();
		let mut parent_window: Option<Window> = None;
		let mut wm_name_str: Option<String> = None;

		let mut i = 0u;
		while i < 10 {
			if current_window.id == 0 {
				break;
			}

			wm_name_str = current_window.get_wm_name();
			match wm_name_str {
				None => {
					let tree = current_window.get_tree();
					parent_window = match tree {
						Some(WindowTree{parent: parent, children: _}) => {
							Some(parent)
						},
						_ => None
					}

				},
				Some(ref wmname) => {
					// wm_name_str = Some(wmname);
					break;
				}
			}

			current_window = match parent_window {
				Some(win) => win,
				_ => current_window
			};

			i += 1;
		}
		match wm_name_str {
			Some(ref name) => {
				println!("WM_NAME: {}", *name);
			},
			None => {
				println!("WM_NAME: none");
			}
		}

		XRecordFreeData(raw_data);
	}
	println!("\n");
}

// ============================================================================
// Simple naive wrappers around X stuff
// ============================================================================
struct Display<'a> {
    display: *mut xlib::Display,
}

impl<'a> Display<'a> {
	fn new() -> Display<'a> {
		Display {display: unsafe {
			let mut a:  i8 = 0;
			let dpy = XOpenDisplay(&a);
			if dpy.is_null() {
				panic!("XOpenDisplay() failed!");
			}
			dpy
		}}
	}
	fn get_input_focus(&self) -> Window{
		let current_window: *mut xlib::Window = &mut 0;
		let revert_to_return: *mut i32 = &mut 0;
		unsafe{xlib::XGetInputFocus(self.display, current_window, revert_to_return)};
		Window {id: unsafe{*current_window}, display: self.display}
	}
}

struct Window<'a> {
    id: u64, // XID
    display: *mut xlib::Display
}

impl<'a> Window<'a> {
	fn get_wm_name(&self) -> Option<String> {
		let mut a:String = String::new();
		let wmname = unsafe {
			let mut window_name: *mut i8 = 0 as *mut i8;
			let res = xlib::XFetchName(self.display, self.id, &mut window_name);
			if res != 0 {
				let c_wm_name = CString::new(std::mem::transmute(window_name), false);
				// xlib::XFree(&mut window_name);
				Some(String::from_str(c_wm_name.as_str().unwrap()))

			} else {
				None
			}
		};
		wmname
	}
	fn get_tree (&self) -> Option<WindowTree> {
		unsafe {
			let mut root: xlib::Window = 0;
			let mut parent: xlib::Window = 0;
			let mut children: *mut xlib::Window = &mut 0u64;
			let mut nchildren: u32 = 0;

			let res = xlib::XQueryTree(
				self.display,
				self.id,
				&mut root,
				&mut parent,
				&mut children,
				&mut nchildren);

			match res {
				0 => None,
				_ => {
					let childs = match nchildren {
						0 => None,
						_ => {
							// let c = std::c_vec::CVec::new(children, nchildren);
							let mut b: Vec<Window> = Vec::new();
							for i in range(0, nchildren as int){
								b.push(Window{
									id: *children.offset(i),
									display: self.display
								});
							}
							Some(b)
						}
					};

					Some(WindowTree {
						parent: Window{
							id: parent,
							display: self.display,
						},
						children: childs
					})
				}
			}
		}
	}
}

struct WindowTree<'a> {
    parent: Window<'a>,
    children: Option<Vec<Window<'a>>>,
}<|endoftext|>