<commit_before>extern crate libc;
extern crate clap;
extern crate clipboard;
extern crate ncurses;
extern crate mmo_parser_backend;
#[macro_use]
extern crate json;
#[macro_use]
extern crate log;
extern crate env_logger;

use std::sync::mpsc::{self};

use std::ffi::{CString, CStr};
use std::os::raw::c_char;

use clap::{Arg, App};

use clipboard::ClipboardContext;
use clipboard::ClipboardProvider;

use ncurses::*;

use std::{thread, time};

mod ui;

use mmo_parser_backend::eventloop::event_loop;
use log::LogLevel;

fn speak(data: &CStr) {
    extern { fn system(data: *const c_char); }

    unsafe { system(data.as_ptr()) }
}

fn main()
{
    env_logger::init().unwrap();
    let matches = App::new("mmo_parser_cli")
        .version("0.1.0")
        .author("Bergman. <Morindhal@gmail.com>")
        .about("Parses MMO logs")
            .arg(Arg::with_name("FILE")
                .help("Sets the log-file to use")
                .required(true)
                .index(1))
            .arg(Arg::with_name("player")
                .required(true)
                .help("Sets the character name to parse, this only catches the YOU and YOUR lines"))
            .arg(Arg::with_name("GAME"))
                .help("Sets the game to parse, EQ2 is default")
        .get_matches();
    /*Set log-file and player whos view the combat is parsed from based on CL input, player should be replaced with a name collected from the file-string*/
    let from_file = match matches.value_of("FILE") {Some(f) => f , None => {warn!("Unable to parse FILE into a variable."); panic!("Unable to parse FILE into a variable");}};
    let player = match matches.value_of("player") {Some(p) => p , None => {warn!("Unable to parse FILE into a variable."); "NONAME"}};
    let player_display = String::from(player);

    let (send_data_request, recieve_answer) = event_loop::new(String::from(from_file), String::from(player));

    /*start the n-curses UI*/
    initscr();
    keypad(stdscr(), true);
    noecho();
    start_color();
    init_pair(1, COLOR_RED, COLOR_BLACK);

    let (input_send, input_recieve) = mpsc::channel();
    //let (timer_tx, timer_rx) = mpsc::channel();

    thread::spawn(move ||
    {
        'input: loop/*Listen to input, send input to main*/
        {
            match input_send.send(getch()) {Ok(_) => {}, Err(e) => {warn!("Error sending input to the UI-sync thread :  {}", e);}};
        }
    });

/*    let timer_counter = thread::spawn(move ||
    {
        let timeout = time::Duration::from_millis(500);
        let mut time = time::Instant::now();
        let mut timers: HashMap<&str, i32> = HashMap::new();
        'timer: loop//Listen to timers, give tts warning when time is up
        {
            match timer_rx.recv_timeout()
            {
                Ok(val) =>
                {
                    //add countdown timer
                    timers.insert(val);
                },
                Err(e) => {}
            }
            for (text, time) in timers
            {
                if time <= 0
                {
                    time -= time.elapsed().as_secs();
                    speak(&CString::new(format!("espeak \"{}\"", text)).unwrap());
                    timers.remove(&text);
                }
            }
        }
    });*/

    /*
    * Send request for data
    * recieve data, wait for data until data is sent.
    */
    let mut jsonobject:json::JsonValue;
    match send_data_request.send(Box::new(object!{"initial" => true})) {Ok(_) => {}, Err(e) => {warn!("Error sending input to the parser backend :  {}", e);}};
    jsonobject = *(recieve_answer.recv().unwrap());

    let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap();
    let timeout = time::Duration::from_millis(1);
    let mut ui_data = ui::UiData{nav_xy: vec![(0,0,ui::PrimaryView::EncounterList)], nav_lock_encounter: false, nav_lock_combatant: false, nav_lock_filter: false, nav_lock_refresh: true, nav_main_win_scroll: (0, 0), nav_encounter_win_scroll: (5, 0), filters: String::from(""), debug: false};
    let mut update_ui = true;

    let mut update_tick = time::Instant::now();

    'ui: loop
    {
        match input_recieve.recv_timeout(timeout)
        {
            Ok(val) => match val
                {
                    27 => // escape
                    {
                        endwin();
                        std::process::exit(1);
                    },
                    99 | 67 =>  // C | c
                    {
                        if !ui_data.is_locked()
                        {
                            match ctx.set_contents(format!("{}",
                                {
                                    let duration = jsonobject["EncounterList"][ui_data.nav_xy.last().unwrap().0 as usize]["Duration"].as_f64().unwrap_or(0f64);
                                    let mut returnstring = format!("Duration: {}:{}\n", (duration/60f64) as usize, duration%60f64);
                                    for combatant in jsonobject["EncounterSpecific"].members()
                                    {
                                        let dps = match duration{0.0=>0.0, _=>(combatant["Damage"].as_f64().unwrap_or(0f64) / duration)/1000000.0  };
                                        returnstring += format!("{name:.4}: {dps:.1}m\n", name=combatant["Name"].as_str().unwrap(), dps=dps).as_str();
                                    }returnstring}))
                                {
                                    Ok(_)=>
                                    {
                                        /*This is currently linux dependant, probably not the best idea for future alerts but for now it "works" assuming one has the correct file on the system*/
                                        speak(&CString::new(format!("paplay /usr/share/sounds/freedesktop/stereo/message.oga")).unwrap());
                                    },
                                    Err(e)=>{warn!("ERROR with the clipboard : {}", e);}
                                };
                        }
                        else
                        {
                            ui_data.filters.push( val as u8 as char );
                            update_ui = true;
                        }
                    },
                    KEY_UP =>
                    {
                        if ui_data.nav_xy.last_mut().unwrap().0 > 0 {
                            ui_data.up();
                            update_ui = true;
                        }
                    },
                    KEY_DOWN =>
                    {
                        if ui_data.nav_lock_encounter && ui_data.nav_xy[1].0 < jsonobject["EncounterSpecific"].len() as i32 - 1
                        {
                            ui_data.nav_xy.last_mut().unwrap().0 += 1;
                        }
                        else if ui_data.nav_lock_combatant
                        {
                            ui_data.nav_xy.last_mut().unwrap().0 += 1;
                        }
                        else if !ui_data.is_locked() && ui_data.nav_xy.last().unwrap().0 < jsonobject["EncounterList"].len() as i32 - 1
                        {
                            if ui_data.nav_encounter_win_scroll.1 > 0
                            {
                                ui_data.nav_encounter_win_scroll.1 -= 1;
                            }
                            else
                            {
                                ui_data.nav_xy.last_mut().unwrap().0 += 1;
                            }
                        }
                        update_ui = true;
                    },/*
                    KEY_LEFT =>
                    {
                        if !ui_data.nav_lock_filter
                        {
                            if ui_data.nav_xy.last().unwrap().1 == 1 && ui_data.nav_lock_encounter && !ui_data.nav_lock_combatant
                            {
                                ui_data.nav_xy.last().unwrap().1 = 0;
                                update_ui = true;
                            }
                        }
                    },
                    KEY_RIGHT =>
                    {
                        if !ui_data.nav_lock_filter
                        {
                            if ui_data.nav_lock_encounter && ui_data.nav_xy.last().unwrap().0 < encounters.len() as i32
                            {
                                ui_data.deeper();
                                ui_data.unlock();
                                ui_data.nav_lock_combatant = true;
                                    update_ui = true;
                            }
                        }
                    },*/
                    10 => // enter
                    {
                        if ui_data.nav_lock_filter
                        {
                            ui_data.surface();
                            ui_data.nav_lock_filter = false;
                        }
                        else if ui_data.nav_lock_encounter
                        {
                            ui_data.deeper();
                            ui_data.nav_lock_encounter = false;
                            ui_data.nav_lock_combatant = true;
                        }
                        else if !ui_data.is_locked()
                        {
                            if ui_data.nav_xy.last().unwrap().0 == jsonobject["EncounterList"].len() as i32
                            {
                                ui_data.nav_xy.last_mut().unwrap().0 -= 1; // ugly code, will bug around --- needs fix
                            }
                            ui_data.deeper();
                            ui_data.nav_lock_encounter = true;
                        }
                        update_ui = true;
                    },
                    KEY_BACKSPACE =>
                    {
                        if !ui_data.nav_lock_filter
                        {
                            if ui_data.nav_lock_encounter
                            {
                                ui_data.surface();
                                ui_data.nav_lock_encounter = false;
                            }
                            else if ui_data.nav_lock_combatant
                            {
                                ui_data.surface();
                                ui_data.nav_lock_encounter = true;
                                ui_data.nav_lock_combatant = false;
                            }
                            else  //backspace with ui_data.nav_xy having a len() of 1
                            {
                            }
                        }
                        else
                        {
                            ui_data.filters.pop();
                        }
                        update_ui = true;
                    },
                    43 => // + key
                    {
                        if !ui_data.nav_lock_filter
                        {
                            ui_data.deeper();
                            ui_data.nav_lock_filter = true;
                            update_ui = true;
                        }
                        else
                        {
                            ui_data.filters.push( val as u8 as char );
                            update_ui = true;
                        }
                    },
                    9 => // TAB key
                    {
                        if ui_data.nav_lock_refresh
                        {
                            ui_data.nav_lock_refresh = false;
                        }
                        else
                        {
                            ui_data.nav_lock_refresh = true;
                        }
                        update_ui = true;
                    },
                    _ =>
                    {
                        //ui_update(&format!("{}", val), &player_display, &mut ui_data, &encounters);
                        if ui_data.nav_lock_filter
                        {
                            ui_data.filters.push( val as u8 as char );
                            update_ui = true;
                        }
                    }
                    //ui_update(&format!("{}", val), &player_display, &mut ui_data, &encounters);}//ui_update(&format!("{}", encounters[0].attackers.len()), &player_display, &pointer, &encounters, &current_encounter);}//}
                },
            Err(e) => {warn!("ERROR recieving data from user input : {}", e);}
        }
        if update_ui || update_tick.elapsed() >= time::Duration::from_millis(3000)
        {
            update_tick = time::Instant::now();
            send_data_request.send(Box::new(ui_data.jsonify())).unwrap();// {Ok(_) => {}, Err(e) => {warn!("Error sending input to the parser backend :  {}", e);}};
            match recieve_answer.recv()
            {
                Ok(val) =>
                {
                    jsonobject = *val;
                    if ui_data.nav_xy.last().unwrap().2 == ui::PrimaryView::EncounterList && !update_ui// && !ui_data.nav_lock_refresh
                    {
                        ui_data.nav_xy[0].0 = jsonobject["EncounterList"].len() as i32 - 1;
                        ui_data.nav_encounter_win_scroll.1 = 0;
                    }
                    update_ui = true;
                },
                Err(e) => {}//warn!("ERROR recieving answer from the parser backend : {}", e);}
            }
        }
        /*
        * parse what data the program is currently interested in and send that in a request to the Sender recieved from mmo_parser_backend
        * parse the response and update the display.
        *
        * do this procedure ONLY if a request for new data has been sent.
        *
        * poll for update every X secs.
        * OR
        * listen to updates from the parser.
        */
        if update_ui
        {
            ui::ui_draw(&player_display, &jsonobject, &mut ui_data);
            update_ui = false;
        }
        thread::sleep(time::Duration::from_millis(100));
    }

}

/*
(1477272172)[Mon Oct 24 03:22:52 2016] YOUR Raging Whirlwind multi attacks Bonesnapper for a critical of 38996 cold damage.\r\n
(1477272172)[Mon Oct 24 03:22:52 2016] Kabernet\'s Asylum multi attacks Bonesnapper for a critical of 36622 mental damage.\r\n
(1478123041)[Wed Nov  2 22:44:01 2016] YOU hit training dummy for a critical of 377262 heat damage.
(1478123041)[Wed Nov  2 22:44:01 2016] YOU multi attack training dummy for a critical of 148320 heat damage.
(1478706458)[Wed Nov  9 16:47:38 2016] YOUR Noxious Rending IV hits training dummy for 875382 disease damage.
(1478132824)[Thu Nov  3 01:27:04 2016] Devorstator's Impatience heals Devorstator for 43947 hit points.
YOU aoe attack training dummy for a critical of 204585 heat damage.

println!("{}",buffer) <-- add this to the match X.captures() statement in the None body. Also needs to disable the code in the ui_update function.

send a request-JSON-object to the parser, this contains a wish-list of what I want.
    The response is then forwarded to the update_ui function and drawn.
        This has the benefit of being doable even if the response doesn't exactly match expectations.
    This request changes depending on user input.
        Limit how many resonses that are wanted by low+count?

TODO: Retool navigation.

*/
<commit_msg>Tweaked the export to clipboard format a bit more.<commit_after>extern crate libc;
extern crate clap;
extern crate clipboard;
extern crate ncurses;
extern crate mmo_parser_backend;
#[macro_use]
extern crate json;
#[macro_use]
extern crate log;
extern crate env_logger;

use std::sync::mpsc::{self};

use std::ffi::{CString, CStr};
use std::os::raw::c_char;

use clap::{Arg, App};

use clipboard::ClipboardContext;
use clipboard::ClipboardProvider;

use ncurses::*;

use std::{thread, time};

mod ui;

use mmo_parser_backend::eventloop::event_loop;
use log::LogLevel;

fn speak(data: &CStr) {
    extern { fn system(data: *const c_char); }

    unsafe { system(data.as_ptr()) }
}

fn main()
{
    env_logger::init().unwrap();
    let matches = App::new("mmo_parser_cli")
        .version("0.1.0")
        .author("Bergman. <Morindhal@gmail.com>")
        .about("Parses MMO logs")
            .arg(Arg::with_name("FILE")
                .help("Sets the log-file to use")
                .required(true)
                .index(1))
            .arg(Arg::with_name("player")
                .required(true)
                .help("Sets the character name to parse, this only catches the YOU and YOUR lines"))
            .arg(Arg::with_name("GAME"))
                .help("Sets the game to parse, EQ2 is default")
        .get_matches();
    /*Set log-file and player whos view the combat is parsed from based on CL input, player should be replaced with a name collected from the file-string*/
    let from_file = match matches.value_of("FILE") {Some(f) => f , None => {warn!("Unable to parse FILE into a variable."); panic!("Unable to parse FILE into a variable");}};
    let player = match matches.value_of("player") {Some(p) => p , None => {warn!("Unable to parse FILE into a variable."); "NONAME"}};
    let player_display = String::from(player);

    let (send_data_request, recieve_answer) = event_loop::new(String::from(from_file), String::from(player));

    /*start the n-curses UI*/
    initscr();
    keypad(stdscr(), true);
    noecho();
    start_color();
    init_pair(1, COLOR_RED, COLOR_BLACK);

    let (input_send, input_recieve) = mpsc::channel();
    //let (timer_tx, timer_rx) = mpsc::channel();

    thread::spawn(move ||
    {
        'input: loop/*Listen to input, send input to main*/
        {
            match input_send.send(getch()) {Ok(_) => {}, Err(e) => {warn!("Error sending input to the UI-sync thread :  {}", e);}};
        }
    });

/*    let timer_counter = thread::spawn(move ||
    {
        let timeout = time::Duration::from_millis(500);
        let mut time = time::Instant::now();
        let mut timers: HashMap<&str, i32> = HashMap::new();
        'timer: loop//Listen to timers, give tts warning when time is up
        {
            match timer_rx.recv_timeout()
            {
                Ok(val) =>
                {
                    //add countdown timer
                    timers.insert(val);
                },
                Err(e) => {}
            }
            for (text, time) in timers
            {
                if time <= 0
                {
                    time -= time.elapsed().as_secs();
                    speak(&CString::new(format!("espeak \"{}\"", text)).unwrap());
                    timers.remove(&text);
                }
            }
        }
    });*/

    /*
    * Send request for data
    * recieve data, wait for data until data is sent.
    */
    let mut jsonobject:json::JsonValue;
    match send_data_request.send(Box::new(object!{"initial" => true})) {Ok(_) => {}, Err(e) => {warn!("Error sending input to the parser backend :  {}", e);}};
    jsonobject = *(recieve_answer.recv().unwrap());

    let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap();
    let timeout = time::Duration::from_millis(1);
    let mut ui_data = ui::UiData{nav_xy: vec![(0,0,ui::PrimaryView::EncounterList)], nav_lock_encounter: false, nav_lock_combatant: false, nav_lock_filter: false, nav_lock_refresh: true, nav_main_win_scroll: (0, 0), nav_encounter_win_scroll: (5, 0), filters: String::from(""), debug: false};
    let mut update_ui = true;

    let mut update_tick = time::Instant::now();

    'ui: loop
    {
        match input_recieve.recv_timeout(timeout)
        {
            Ok(val) => match val
                {
                    27 => // escape
                    {
                        endwin();
                        std::process::exit(1);
                    },
                    99 | 67 =>  // C | c
                    {
                        if !ui_data.is_locked()
                        {
                            match ctx.set_contents(format!("{}",
                                {
                                    let duration = jsonobject["EncounterList"][ui_data.nav_xy.last().unwrap().0 as usize]["Duration"].as_f64().unwrap_or(0f64);
                                    let mut returnstring = format!("Duration: {}:{}\n", (duration/60f64) as usize, duration%60f64);
                                    for combatant in jsonobject["EncounterSpecific"].members()
                                    {
                                        let dps = match duration{0.0=>0.0, _=>(combatant["Damage"].as_f64().unwrap_or(0f64) / duration)/1000000.0  };
                                        returnstring += format!("{name:.4}: {dps:.0}m\n", name=combatant["Name"].as_str().unwrap(), dps=dps).as_str();
                                    }returnstring}))
                                {
                                    Ok(_)=>
                                    {
                                        /*This is currently linux dependant, probably not the best idea for future alerts but for now it "works" assuming one has the correct file on the system*/
                                        speak(&CString::new(format!("paplay /usr/share/sounds/freedesktop/stereo/message.oga")).unwrap());
                                    },
                                    Err(e)=>{warn!("ERROR with the clipboard : {}", e);}
                                };
                        }
                        else
                        {
                            ui_data.filters.push( val as u8 as char );
                            update_ui = true;
                        }
                    },
                    KEY_UP =>
                    {
                        if ui_data.nav_xy.last_mut().unwrap().0 > 0 {
                            ui_data.up();
                            update_ui = true;
                        }
                    },
                    KEY_DOWN =>
                    {
                        if ui_data.nav_lock_encounter && ui_data.nav_xy[1].0 < jsonobject["EncounterSpecific"].len() as i32 - 1
                        {
                            ui_data.nav_xy.last_mut().unwrap().0 += 1;
                        }
                        else if ui_data.nav_lock_combatant
                        {
                            ui_data.nav_xy.last_mut().unwrap().0 += 1;
                        }
                        else if !ui_data.is_locked() && ui_data.nav_xy.last().unwrap().0 < jsonobject["EncounterList"].len() as i32 - 1
                        {
                            if ui_data.nav_encounter_win_scroll.1 > 0
                            {
                                ui_data.nav_encounter_win_scroll.1 -= 1;
                            }
                            else
                            {
                                ui_data.nav_xy.last_mut().unwrap().0 += 1;
                            }
                        }
                        update_ui = true;
                    },/*
                    KEY_LEFT =>
                    {
                        if !ui_data.nav_lock_filter
                        {
                            if ui_data.nav_xy.last().unwrap().1 == 1 && ui_data.nav_lock_encounter && !ui_data.nav_lock_combatant
                            {
                                ui_data.nav_xy.last().unwrap().1 = 0;
                                update_ui = true;
                            }
                        }
                    },
                    KEY_RIGHT =>
                    {
                        if !ui_data.nav_lock_filter
                        {
                            if ui_data.nav_lock_encounter && ui_data.nav_xy.last().unwrap().0 < encounters.len() as i32
                            {
                                ui_data.deeper();
                                ui_data.unlock();
                                ui_data.nav_lock_combatant = true;
                                    update_ui = true;
                            }
                        }
                    },*/
                    10 => // enter
                    {
                        if ui_data.nav_lock_filter
                        {
                            ui_data.surface();
                            ui_data.nav_lock_filter = false;
                        }
                        else if ui_data.nav_lock_encounter
                        {
                            ui_data.deeper();
                            ui_data.nav_lock_encounter = false;
                            ui_data.nav_lock_combatant = true;
                        }
                        else if !ui_data.is_locked()
                        {
                            if ui_data.nav_xy.last().unwrap().0 == jsonobject["EncounterList"].len() as i32
                            {
                                ui_data.nav_xy.last_mut().unwrap().0 -= 1; // ugly code, will bug around --- needs fix
                            }
                            ui_data.deeper();
                            ui_data.nav_lock_encounter = true;
                        }
                        update_ui = true;
                    },
                    KEY_BACKSPACE =>
                    {
                        if !ui_data.nav_lock_filter
                        {
                            if ui_data.nav_lock_encounter
                            {
                                ui_data.surface();
                                ui_data.nav_lock_encounter = false;
                            }
                            else if ui_data.nav_lock_combatant
                            {
                                ui_data.surface();
                                ui_data.nav_lock_encounter = true;
                                ui_data.nav_lock_combatant = false;
                            }
                            else  //backspace with ui_data.nav_xy having a len() of 1
                            {
                            }
                        }
                        else
                        {
                            ui_data.filters.pop();
                        }
                        update_ui = true;
                    },
                    43 => // + key
                    {
                        if !ui_data.nav_lock_filter
                        {
                            ui_data.deeper();
                            ui_data.nav_lock_filter = true;
                            update_ui = true;
                        }
                        else
                        {
                            ui_data.filters.push( val as u8 as char );
                            update_ui = true;
                        }
                    },
                    9 => // TAB key
                    {
                        if ui_data.nav_lock_refresh
                        {
                            ui_data.nav_lock_refresh = false;
                        }
                        else
                        {
                            ui_data.nav_lock_refresh = true;
                        }
                        update_ui = true;
                    },
                    _ =>
                    {
                        //ui_update(&format!("{}", val), &player_display, &mut ui_data, &encounters);
                        if ui_data.nav_lock_filter
                        {
                            ui_data.filters.push( val as u8 as char );
                            update_ui = true;
                        }
                    }
                    //ui_update(&format!("{}", val), &player_display, &mut ui_data, &encounters);}//ui_update(&format!("{}", encounters[0].attackers.len()), &player_display, &pointer, &encounters, &current_encounter);}//}
                },
            Err(e) => {warn!("ERROR recieving data from user input : {}", e);}
        }
        if update_ui || update_tick.elapsed() >= time::Duration::from_millis(3000)
        {
            update_tick = time::Instant::now();
            send_data_request.send(Box::new(ui_data.jsonify())).unwrap();// {Ok(_) => {}, Err(e) => {warn!("Error sending input to the parser backend :  {}", e);}};
            match recieve_answer.recv()
            {
                Ok(val) =>
                {
                    jsonobject = *val;
                    if ui_data.nav_xy.last().unwrap().2 == ui::PrimaryView::EncounterList && !update_ui// && !ui_data.nav_lock_refresh
                    {
                        ui_data.nav_xy[0].0 = jsonobject["EncounterList"].len() as i32 - 1;
                        ui_data.nav_encounter_win_scroll.1 = 0;
                    }
                    update_ui = true;
                },
                Err(e) => {}//warn!("ERROR recieving answer from the parser backend : {}", e);}
            }
        }
        /*
        * parse what data the program is currently interested in and send that in a request to the Sender recieved from mmo_parser_backend
        * parse the response and update the display.
        *
        * do this procedure ONLY if a request for new data has been sent.
        *
        * poll for update every X secs.
        * OR
        * listen to updates from the parser.
        */
        if update_ui
        {
            ui::ui_draw(&player_display, &jsonobject, &mut ui_data);
            update_ui = false;
        }
        thread::sleep(time::Duration::from_millis(100));
    }

}

/*
(1477272172)[Mon Oct 24 03:22:52 2016] YOUR Raging Whirlwind multi attacks Bonesnapper for a critical of 38996 cold damage.\r\n
(1477272172)[Mon Oct 24 03:22:52 2016] Kabernet\'s Asylum multi attacks Bonesnapper for a critical of 36622 mental damage.\r\n
(1478123041)[Wed Nov  2 22:44:01 2016] YOU hit training dummy for a critical of 377262 heat damage.
(1478123041)[Wed Nov  2 22:44:01 2016] YOU multi attack training dummy for a critical of 148320 heat damage.
(1478706458)[Wed Nov  9 16:47:38 2016] YOUR Noxious Rending IV hits training dummy for 875382 disease damage.
(1478132824)[Thu Nov  3 01:27:04 2016] Devorstator's Impatience heals Devorstator for 43947 hit points.
YOU aoe attack training dummy for a critical of 204585 heat damage.

println!("{}",buffer) <-- add this to the match X.captures() statement in the None body. Also needs to disable the code in the ui_update function.

send a request-JSON-object to the parser, this contains a wish-list of what I want.
    The response is then forwarded to the update_ui function and drawn.
        This has the benefit of being doable even if the response doesn't exactly match expectations.
    This request changes depending on user input.
        Limit how many resonses that are wanted by low+count?

TODO: Retool navigation.

*/
<|endoftext|>


<commit_before>/*
    Copyright 2013 Jesse 'Jeaye' Wilkerson
    See licensing in LICENSE file, or at:
        http://www.opensource.org/licenses/BSD-3-Clause

    File: main.rs
    Author: Jesse 'Jeaye' Wilkerson
    Description:
      Entry point.
*/

extern mod extra;
extern mod opengles;
extern mod glfw;
extern mod stb_image;

use std::libc;
use gl2 = opengles::gl2;

/* TODO: Should be able to remove. */
#[cfg(target_os = "linux")]
#[nolink]
#[link_args = "-lX11 -lXrandr -lXi -lXxf86vm"]
extern { }

#[path = "gl/mod.rs"]
pub mod gl;

#[path = "math/mod.rs"]
pub mod math;

#[macro_escape]
#[path = "gl/check.rs"]
mod check;

#[path = "obj/bsp/mod.rs"]
pub mod bsp;

#[path = "ui/mod.rs"]
pub mod ui;

#[path = "obj/primitive/mod.rs"]
pub mod primitive;

#[path = "obj/voxel/mod.rs"]
pub mod voxel;

#[path = "state/mod.rs"]
pub mod state;

fn main()
{
  glfw::set_error_callback(error_callback);

  do glfw::spawn
  {
    glfw::window_hint::context_version(3, 2);
    glfw::window_hint::opengl_profile(glfw::OPENGL_CORE_PROFILE);
    glfw::window_hint::opengl_forward_compat(true);

    let window_res = glfw::Window::create(1024, 768, "Q^3", glfw::Windowed);
    let window = match window_res
    {
      Some(win) => { @win },
      None => { fail!("Failed to create window!") }
    };
    window.make_context_current();

    let _ui_renderer = ui::Renderer::new(window);

    /* Create all the states we need. */
    let states = state::Director::new();
    let console_state = state::Console::new();
    let console_renderer_state = state::Console_Renderer::new(console_state);
    let game_state = state::Game::new();
    let game_renderer_state = state::Game_Renderer::new(game_state, window);
    let bsp_renderer_state = state::BSP_Renderer::new(game_renderer_state);
    states.push(game_state as @mut state::State);
    states.push(game_renderer_state as @mut state::State);
    states.push(console_state as @mut state::State);
    states.push(console_renderer_state as @mut state::State);

    /* Setup callbacks. */ /* TODO: Crash on close with these callbacks. */
    do window.set_focus_callback |_, focused|
    { if focused {window.set_cursor_mode(glfw::CURSOR_DISABLED); } }
    do window.set_cursor_pos_callback |_, x, y|
    { states.mouse_moved(x as f32, y as f32); }
    do window.set_char_callback |_, c|
    { states.key_char(c); }
    do window.set_key_callback |window, key, _scancode, action, mods|
    {
      /* TODO: Ability to pop specific states or insert others. */
      if key == glfw::KEY_LEFT_BRACKET && action == glfw::PRESS
      {
        states.pop(); /* console renderer */
        states.pop(); /* console */
        states.pop(); /* renderer */
        states.push(game_renderer_state as @mut state::State);
        states.push(console_state as @mut state::State);
        states.push(console_renderer_state as @mut state::State);
      }
      else if key == glfw::KEY_RIGHT_BRACKET && action == glfw::PRESS
      {
        states.pop(); /* console renderer */
        states.pop(); /* console */
        states.pop(); /* renderer */
        states.push(bsp_renderer_state as @mut state::State);
        states.push(console_state as @mut state::State);
        states.push(console_renderer_state as @mut state::State);
      }

      states.key_action(key, action, mods);
      key_callback(window, key, action);
    }

    /* Console functions. */
    ui::Console_Activator::get().add_accessor("q3.version", |_|
    { fmt!("%s.%s", env!("VERSION"), env!("COMMIT")) });

    /* Delta time. */
    let mut cur_time = extra::time::precise_time_s() as f32;
    let mut last_time = cur_time;

    while !window.should_close()
    {
      glfw::poll_events();

      let delta = cur_time - last_time;
      last_time = cur_time;
      cur_time = extra::time::precise_time_s() as f32;

      states.update(delta);

      check!(gl2::clear(gl2::COLOR_BUFFER_BIT | gl2::DEPTH_BUFFER_BIT));
      {
        states.render();
      } window.swap_buffers();
    }
  }
}

fn key_callback(window: &glfw::Window, key: libc::c_int, action: libc::c_int)
{
  if action == glfw::PRESS && key == glfw::KEY_ESCAPE
  { window.set_should_close(true); }
}

fn error_callback(error: libc::c_int, description: ~str)
{ error!("GLFW Error %?: %s", error, description); }

<commit_msg> #22 Removed unneeded linker options<commit_after>/*
    Copyright 2013 Jesse 'Jeaye' Wilkerson
    See licensing in LICENSE file, or at:
        http://www.opensource.org/licenses/BSD-3-Clause

    File: main.rs
    Author: Jesse 'Jeaye' Wilkerson
    Description:
      Entry point.
*/

extern mod extra;
extern mod opengles;
extern mod glfw;
extern mod stb_image;

use std::libc;
use gl2 = opengles::gl2;

#[path = "gl/mod.rs"]
pub mod gl;

#[path = "math/mod.rs"]
pub mod math;

#[macro_escape]
#[path = "gl/check.rs"]
mod check;

#[path = "obj/bsp/mod.rs"]
pub mod bsp;

#[path = "ui/mod.rs"]
pub mod ui;

#[path = "obj/primitive/mod.rs"]
pub mod primitive;

#[path = "obj/voxel/mod.rs"]
pub mod voxel;

#[path = "state/mod.rs"]
pub mod state;

fn main()
{
  glfw::set_error_callback(error_callback);

  do glfw::spawn
  {
    glfw::window_hint::context_version(3, 2);
    glfw::window_hint::opengl_profile(glfw::OPENGL_CORE_PROFILE);
    glfw::window_hint::opengl_forward_compat(true);

    let window_res = glfw::Window::create(1024, 768, "Q^3", glfw::Windowed);
    let window = match window_res
    {
      Some(win) => { @win },
      None => { fail!("Failed to create window!") }
    };
    window.make_context_current();

    let _ui_renderer = ui::Renderer::new(window);

    /* Create all the states we need. */
    let states = state::Director::new();
    let console_state = state::Console::new();
    let console_renderer_state = state::Console_Renderer::new(console_state);
    let game_state = state::Game::new();
    let game_renderer_state = state::Game_Renderer::new(game_state, window);
    let bsp_renderer_state = state::BSP_Renderer::new(game_renderer_state);
    states.push(game_state as @mut state::State);
    states.push(game_renderer_state as @mut state::State);
    states.push(console_state as @mut state::State);
    states.push(console_renderer_state as @mut state::State);

    /* Setup callbacks. */ /* TODO: Crash on close with these callbacks. */
    do window.set_focus_callback |_, focused|
    { if focused {window.set_cursor_mode(glfw::CURSOR_DISABLED); } }
    do window.set_cursor_pos_callback |_, x, y|
    { states.mouse_moved(x as f32, y as f32); }
    do window.set_char_callback |_, c|
    { states.key_char(c); }
    do window.set_key_callback |window, key, _scancode, action, mods|
    {
      /* TODO: Ability to pop specific states or insert others. */
      if key == glfw::KEY_LEFT_BRACKET && action == glfw::PRESS
      {
        states.pop(); /* console renderer */
        states.pop(); /* console */
        states.pop(); /* renderer */
        states.push(game_renderer_state as @mut state::State);
        states.push(console_state as @mut state::State);
        states.push(console_renderer_state as @mut state::State);
      }
      else if key == glfw::KEY_RIGHT_BRACKET && action == glfw::PRESS
      {
        states.pop(); /* console renderer */
        states.pop(); /* console */
        states.pop(); /* renderer */
        states.push(bsp_renderer_state as @mut state::State);
        states.push(console_state as @mut state::State);
        states.push(console_renderer_state as @mut state::State);
      }

      states.key_action(key, action, mods);
      key_callback(window, key, action);
    }

    /* Console functions. */
    ui::Console_Activator::get().add_accessor("q3.version", |_|
    { fmt!("%s.%s", env!("VERSION"), env!("COMMIT")) });

    /* Delta time. */
    let mut cur_time = extra::time::precise_time_s() as f32;
    let mut last_time = cur_time;

    while !window.should_close()
    {
      glfw::poll_events();

      let delta = cur_time - last_time;
      last_time = cur_time;
      cur_time = extra::time::precise_time_s() as f32;

      states.update(delta);

      check!(gl2::clear(gl2::COLOR_BUFFER_BIT | gl2::DEPTH_BUFFER_BIT));
      {
        states.render();
      } window.swap_buffers();
    }
  }
}

fn key_callback(window: &glfw::Window, key: libc::c_int, action: libc::c_int)
{
  if action == glfw::PRESS && key == glfw::KEY_ESCAPE
  { window.set_should_close(true); }
}

fn error_callback(error: libc::c_int, description: ~str)
{ error!("GLFW Error %?: %s", error, description); }

<|endoftext|>


<commit_before>#[macro_use]
extern crate glium;
extern crate image;
extern crate spine;
extern crate cgmath;

use std::collections::HashMap;
use glium::{DisplayBuild, Surface};
use glium::index::PrimitiveType;
use spine::atlas::Atlas;
use cgmath::Matrix4;

use std::fs::File;
use std::io::BufReader;

mod run;

#[derive(Copy, Clone, Debug)]
struct Vertex {
    position: [f32; 2],
    tex_coords: [f32; 2],
}

implement_vertex!(Vertex, position, tex_coords);

/// insert all atlas textures into vertices
/// returns attachment (name, index) defining 4 consecutive vertices
fn load_atlas(atlas_src: &str, width: u32, height: u32,
              vertices: &mut Vec<Vertex>,
              indices: &mut Vec<u32>)
    -> HashMap<String, u32>
{
    let (width, height) = (width as f32, height as f32);

    // helper closure to convert atlas texture coordinates into vertex texture's
    let to_tex_coords = |x: u16, y: u16| {
        let (x, y) = (x as f32, y as f32);
        [x / width, 1.0 - y / height]
    };

    // iterates over atlas textures and convert it to centered rectangle (4 vertices)
    let mut textures = HashMap::new();
    let mut n = vertices.len() as u32;
    for (name, t) in Atlas::from_file(atlas_src).expect("cannot find atlas file").into_iter() {

        let tex0 = to_tex_coords(t.xy.0,            t.xy.1);
        let tex1 = to_tex_coords(t.xy.0 + t.size.0, t.xy.1);
        let tex2 = to_tex_coords(t.xy.0 + t.size.0, t.xy.1 + t.size.1);
        let tex3 = to_tex_coords(t.xy.0,            t.xy.1 + t.size.1);
        // get 4 vertices defining rectangle texture, centered per default
        let (dx, dy) = (t.xy.0 as f32 / 2.0 / width, t.xy.1 as f32 / 2.0 / height);

        vertices.push(Vertex { position: [-dx, dy], tex_coords: tex0 });
        vertices.push(Vertex { position: [dx, dy], tex_coords: tex1 });
        vertices.push(Vertex { position: [dx, -dy], tex_coords: tex2 });
        vertices.push(Vertex { position: [-dx, -dy], tex_coords: tex3 });
        textures.insert(name, n);

        indices.push(n);
        indices.push(n + 1);
        indices.push(n + 2);
        indices.push(n + 2);
        indices.push(n + 3);
        indices.push(n);

        n += 4;
    }
    textures
}

fn to_matrix(srt: &spine::skeleton::SRT) -> Matrix4<f32> {
    use cgmath::{Matrix2, Vector3};

    let scale_matrix = Matrix4::new(srt.scale.0, 0.0, 0.0, 0.0,
                                    0.0, srt.scale.1, 0.0, 0.0,
                                    0.0, 0.0, 1.0, 0.0,
                                    0.0, 0.0, 0.0, 1.0);
    let rotation_matrix = Matrix4::from(Matrix2::from_angle(cgmath::rad(srt.rotation).into()));
    let translation_matrix = Matrix4::from_translation(&Vector3::new(srt.position.0, srt.position.1, 0.0));

    translation_matrix * rotation_matrix * scale_matrix
}

fn apply_sprite(sprites: &[spine::skeleton::animation::Sprite],
                attachments: &HashMap<String, u32>,
                vertices: &mut glium::VertexBuffer<Vertex>) {

    for (i, chunk) in vertices.map().chunks_mut(4).enumerate() {
        // search corresponding sprite
        if let Some(sprite) = attachments.iter().find(|&(_, n)| *n == 4*i as u32)
                     .and_then(|(name, _)| sprites.iter()
                        .find(|s| &s.attachment == name)) {
            let matrix = to_matrix(&sprite.srt);
            let positions = matrix * Matrix4::new(chunk[0].position[0], chunk[0].position[1], 0.0, 1.0,
                                                  chunk[1].position[0], chunk[1].position[1], 0.0, 1.0,
                                                  chunk[2].position[0], chunk[2].position[1], 0.0, 1.0,
                                                  chunk[3].position[0], chunk[3].position[1], 0.0, 1.0);
            chunk[0].position[0] = positions[0][0];
            chunk[0].position[1] = positions[0][1];
            chunk[1].position[0] = positions[1][0];
            chunk[1].position[1] = positions[1][1];
            chunk[2].position[0] = positions[2][0];
            chunk[2].position[1] = positions[2][1];
            chunk[3].position[0] = positions[3][0];
            chunk[3].position[1] = positions[3][1];
            println!("modifying sprite '{}': {:?}", &sprite.attachment, chunk);
        }
    }

}

// fn draw_identity(texture_names: &HashMap<String, u32>, vertices: &mut Vec<Vertex>,
//     width: u32, height: u32) -> Vec<u32>
// {
//     let (width, height) = (width as f32, height as f32);
//     let tex_to_pos = |t: &[f32; 2]| {
//         [width * (2.0 * t[0] - 1.0), height * (2.0 * t[1] - 1.0)]
//     };
//
//     // Change all default position to texture position
//     for v in vertices.iter_mut() {
//         let pos = tex_to_pos(&v.tex_coords);
//         v.position = pos;
//     }
//
//     // build shapes by manipulating indices
//     // show all shapes for now
//     let mut indices = Vec::with_capacity(texture_names.len() * 6);
//     for (_, n) in texture_names.iter() {
//         indices.push(*n);
//         indices.push(*n + 1);
//         indices.push(*n + 2);
//         indices.push(*n + 2);
//         indices.push(*n + 3);
//         indices.push(*n);
//     }
//
//     indices
// }

fn main() {

    // TODO: parse program arguments
    let atlas_src = "/home/johann/projects/spine-render/example/spineboy.atlas";
    let atlas_img = "/home/johann/projects/spine-render/example/spineboy.png";
    let skeleton_src = "/home/johann/projects/spine-render/example/spineboy.json";

    // prepare glium objects
    let image = image::open(atlas_img).expect("couldn't read atlas image");
    let (width, height) = match image {
        image::DynamicImage::ImageRgba8(ref rgba) => (rgba.width(), rgba.height()),
        ref image => {
            let rgba = image.to_rgba();
            (rgba.width(), rgba.height())
        }
    };
    let window = glium::glutin::WindowBuilder::new().build_glium().unwrap();
    let texture = glium::texture::Texture2d::new(&window, image).unwrap();

    println!("texture: {:?}", texture, );

    // load atlas textures
    let (mut vertices, mut indices) = (Vec::new(), Vec::new());
    let texture_names = load_atlas(atlas_src, width, height, &mut vertices, &mut indices);

    // load skeleton from json
    let f = File::open(skeleton_src).ok().expect("Cannot open json file");
    let reader = BufReader::new(f);
    let skeleton = spine::skeleton::Skeleton::from_reader(reader).ok().expect("error while parsing json");
    let walk = skeleton.get_animated_skin("default", Some("walk")).ok().expect("error while creating animated skin");

    let sprites = walk.interpolate(0.0).expect("default skin should not be empty");

    // draw identity (for test)
    // let indices = draw_identity(&texture_names, &mut vertices, width, height);

    let mut vertex_buffer = glium::VertexBuffer::new(&window, &vertices).unwrap();
    let indices = glium::IndexBuffer::new(&window, PrimitiveType::TrianglesList, &indices).unwrap();

    // transform vertices
    apply_sprite(&sprites, &texture_names, &mut vertex_buffer);

    // opengl vertex program
    let vertex_src = r#"
        #version 140

        in vec2 position;
        in vec2 tex_coords;

        out vec2 v_tex_coords;

        uniform mat2 perspective;

        void main() {
            v_tex_coords = tex_coords;
            gl_Position = vec4(perspective * position, 0.0, 1.0);
        }
    "#;

    // opengl fragment program
    let fragment_src = r#"
        #version 140

        in vec2 v_tex_coords;
        out vec4 color;
        uniform sampler2D tex;

        void main() {
            color = texture(tex, v_tex_coords);
        }
    "#;

    let program = glium::Program::from_source(&window, vertex_src, fragment_src, None).unwrap();

    // the main loop, supposed to run with a constant FPS
    run::start_loop(|| {

        let mut target = window.draw();
        target.clear_color(0.0, 0.0, 1.0, 0.0);

        let perspective = {
            let (width, height) = target.get_dimensions();
            [
                [1.0 / width as f32, 0.0],
                [0.0, 1.0 / height as f32]
            ]
        };

        let uniforms = uniform! {
            tex: &texture,
            perspective: perspective
        };

        let params = glium::DrawParameters {
            blend: glium::Blend::alpha_blending(),
            .. Default::default()
        };

        target.draw(&vertex_buffer, &indices, &program, &uniforms, &params).unwrap();
        target.finish().unwrap();

        for ev in window.poll_events() {
            match ev {
                glium::glutin::Event::Closed => return run::Action::Stop,
                _ => ()
            }
        }

        run::Action::Continue
    });

        // for ev in window.wait_events() {
        //     match ev {
        //         glium::glutin::Event::Closed => return,
        //         _ => ()
        //     }
        // }
}
<commit_msg>update with positions<commit_after>#[macro_use]
extern crate glium;
extern crate image;
extern crate spine;
extern crate cgmath;

use std::collections::HashMap;
use glium::{DisplayBuild, Surface};
use glium::index::PrimitiveType;
use spine::atlas::Atlas;
use cgmath::Matrix4;

use std::fs::File;
use std::io::BufReader;

mod run;

#[derive(Copy, Clone, Debug)]
struct Vertex {
    position: [f32; 2],
    tex_coords: [f32; 2],
}

implement_vertex!(Vertex, position, tex_coords);

/// insert all atlas textures into vertices
/// returns attachment (name, index) defining 4 consecutive vertices
fn load_atlas(atlas_src: &str, width: u32, height: u32,
              vertices: &mut Vec<Vertex>,
              indices: &mut Vec<u32>)
    -> HashMap<String, u32>
{
    let (width, height) = (width as f32, height as f32);

    // helper closure to convert atlas texture coordinates into vertex texture's
    let to_tex_coords = |x: u16, y: u16| {
        let (x, y) = (x as f32, y as f32);
        [x / width, 1.0 - y / height]
    };

    // iterates over atlas textures and convert it to centered rectangle (4 vertices)
    let mut textures = HashMap::new();
    let mut n = vertices.len() as u32;
    for (name, t) in Atlas::from_file(atlas_src).expect("cannot find atlas file").into_iter() {

        let tex0 = to_tex_coords(t.xy.0,            t.xy.1);
        let tex1 = to_tex_coords(t.xy.0 + t.size.0, t.xy.1);
        let tex2 = to_tex_coords(t.xy.0 + t.size.0, t.xy.1 + t.size.1);
        let tex3 = to_tex_coords(t.xy.0,            t.xy.1 + t.size.1);
        // get 4 vertices defining rectangle texture, centered per default
        let (dx, dy) = (t.xy.0 as f32 / 2.0 / width, t.xy.1 as f32 / 2.0 / height);

        vertices.push(Vertex { position: [-dx, dy], tex_coords: tex0 });
        vertices.push(Vertex { position: [dx, dy], tex_coords: tex1 });
        vertices.push(Vertex { position: [dx, -dy], tex_coords: tex2 });
        vertices.push(Vertex { position: [-dx, -dy], tex_coords: tex3 });
        textures.insert(name, n);

        indices.push(n);
        indices.push(n + 1);
        indices.push(n + 2);
        indices.push(n + 2);
        indices.push(n + 3);
        indices.push(n);

        n += 4;
    }
    textures
}

fn apply_sprite(sprites: &[spine::skeleton::animation::Sprite],
                attachments: &HashMap<String, u32>,
                vertices: &mut glium::VertexBuffer<Vertex>) {

    for (i, chunk) in vertices.map().chunks_mut(4).enumerate() {
        // search corresponding sprite
        if let Some(sprite) = attachments.iter().find(|&(_, n)| *n == 4*i as u32)
                     .and_then(|(name, _)| sprites.iter()
                        .find(|s| &s.attachment == name)) {

            let positions = sprite.positions;
            chunk[0].position[0] = positions[0][0];
            chunk[0].position[1] = positions[0][1];
            chunk[1].position[0] = positions[1][0];
            chunk[1].position[1] = positions[1][1];
            chunk[2].position[0] = positions[2][0];
            chunk[2].position[1] = positions[2][1];
            chunk[3].position[0] = positions[3][0];
            chunk[3].position[1] = positions[3][1];
            println!("modifying sprite '{}': {:?}", &sprite.attachment, chunk);
        }
    }

}

// fn draw_identity(texture_names: &HashMap<String, u32>, vertices: &mut Vec<Vertex>,
//     width: u32, height: u32) -> Vec<u32>
// {
//     let (width, height) = (width as f32, height as f32);
//     let tex_to_pos = |t: &[f32; 2]| {
//         [width * (2.0 * t[0] - 1.0), height * (2.0 * t[1] - 1.0)]
//     };
//
//     // Change all default position to texture position
//     for v in vertices.iter_mut() {
//         let pos = tex_to_pos(&v.tex_coords);
//         v.position = pos;
//     }
//
//     // build shapes by manipulating indices
//     // show all shapes for now
//     let mut indices = Vec::with_capacity(texture_names.len() * 6);
//     for (_, n) in texture_names.iter() {
//         indices.push(*n);
//         indices.push(*n + 1);
//         indices.push(*n + 2);
//         indices.push(*n + 2);
//         indices.push(*n + 3);
//         indices.push(*n);
//     }
//
//     indices
// }

fn main() {

    // TODO: parse program arguments
    let atlas_src = "/home/johann/projects/spine-render/example/spineboy.atlas";
    let atlas_img = "/home/johann/projects/spine-render/example/spineboy.png";
    let skeleton_src = "/home/johann/projects/spine-render/example/spineboy.json";

    // prepare glium objects
    let image = image::open(atlas_img).expect("couldn't read atlas image");
    let (width, height) = match image {
        image::DynamicImage::ImageRgba8(ref rgba) => (rgba.width(), rgba.height()),
        ref image => {
            let rgba = image.to_rgba();
            (rgba.width(), rgba.height())
        }
    };
    let window = glium::glutin::WindowBuilder::new().build_glium().unwrap();
    let texture = glium::texture::Texture2d::new(&window, image).unwrap();

    println!("texture: {:?}", texture, );

    // load atlas textures
    let (mut vertices, mut indices) = (Vec::new(), Vec::new());
    let texture_names = load_atlas(atlas_src, width, height, &mut vertices, &mut indices);

    // load skeleton from json
    let f = File::open(skeleton_src).ok().expect("Cannot open json file");
    let reader = BufReader::new(f);
    let skeleton = spine::skeleton::Skeleton::from_reader(reader).ok().expect("error while parsing json");
    let walk = skeleton.get_animated_skin("default", Some("walk")).ok().expect("error while creating animated skin");

    let sprites = walk.interpolate(0.0).expect("default skin should not be empty");

    // draw identity (for test)
    // let indices = draw_identity(&texture_names, &mut vertices, width, height);

    let mut vertex_buffer = glium::VertexBuffer::new(&window, &vertices).unwrap();
    let indices = glium::IndexBuffer::new(&window, PrimitiveType::TrianglesList, &indices).unwrap();

    // transform vertices
    apply_sprite(&sprites, &texture_names, &mut vertex_buffer);

    // opengl vertex program
    let vertex_src = r#"
        #version 140

        in vec2 position;
        in vec2 tex_coords;

        out vec2 v_tex_coords;

        uniform mat2 perspective;

        void main() {
            v_tex_coords = tex_coords;
            gl_Position = vec4(perspective * position, 0.0, 1.0);
        }
    "#;

    // opengl fragment program
    let fragment_src = r#"
        #version 140

        in vec2 v_tex_coords;
        out vec4 color;
        uniform sampler2D tex;

        void main() {
            color = texture(tex, v_tex_coords);
        }
    "#;

    let program = glium::Program::from_source(&window, vertex_src, fragment_src, None).unwrap();

    // the main loop, supposed to run with a constant FPS
    run::start_loop(|| {

        let mut target = window.draw();
        target.clear_color(0.0, 0.0, 1.0, 0.0);

        let perspective = {
            let (width, height) = target.get_dimensions();
            [
                [1.0 / width as f32, 0.0],
                [0.0, 1.0 / height as f32]
            ]
        };

        let uniforms = uniform! {
            tex: &texture,
            perspective: perspective
        };

        let params = glium::DrawParameters {
            blend: glium::Blend::alpha_blending(),
            .. Default::default()
        };

        target.draw(&vertex_buffer, &indices, &program, &uniforms, &params).unwrap();
        target.finish().unwrap();

        for ev in window.poll_events() {
            match ev {
                glium::glutin::Event::Closed => return run::Action::Stop,
                _ => ()
            }
        }

        run::Action::Continue
    });

        // for ev in window.wait_events() {
        //     match ev {
        //         glium::glutin::Event::Closed => return,
        //         _ => ()
        //     }
        // }
}
<|endoftext|>


<commit_before>extern crate clap;

use clap::{Arg, App};
use std::cmp::Ordering::Equal;
use std::error::Error;
use std::f32;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::path::Path;
use std::str::FromStr;

fn print_results(mut timings: Vec<f32>) {
    timings.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Equal));

    let start: usize = 0;
    let end: usize = timings.len() - 1;
    if end == 0 {
        return;
    };
    let median: usize = end / 2;
    let p70: usize = end.checked_mul(70).unwrap().checked_div(100).unwrap();
    let p80: usize = end.checked_mul(80).unwrap().checked_div(100).unwrap();
    let p95: usize = end.checked_mul(95).unwrap().checked_div(100).unwrap();
    let p99: usize = end.checked_mul(99).unwrap().checked_div(100).unwrap();

    println!("");
    println!("Results:");
    println!("    Total count:   {}", timings.len());
    println!("    Min:           {}", timings[start]);
    println!("    Median:        {} ({})", timings[median], median + 1);
    println!("    70 percentile: {} ({})", timings[p70], p70 + 1);
    println!("    80 percentile: {} ({})", timings[p80], p80 + 1);
    println!("    95 percentile: {} ({})", timings[p95], p95 + 1);
    println!("    99 percentile: {} ({})", timings[p99], p99 + 1);
    println!("    Max:           {} ({})", timings[end], end + 1);
}

// This is the main function
fn main() {

	let matches = App::new("Calculate response time percentiles on logs")
		.version("0.0.1")
		.author("Vladimir Prokhoda <vladimirbright@gmail.com>")
		.arg(Arg::with_name("separator")
		   .short("s")
		   .long("separator")
           .default_value(" ")
		   .help("Column separator")
		   .takes_value(true))
		.arg(Arg::with_name("column")
		   .short("c")
		   .long("column")
           .default_value("8")
		   .use_delimiter(false)
		   .help("Column number to use")
		   .takes_value(true))
		.arg(Arg::with_name("input")
		   .help("Sets the input file to use")
		   .required(true)
		   .index(1))
		.arg(Arg::with_name("print")
		   .short("p")
		   .long("print")
		   .help("Print matched rows"))
		.get_matches();

    let sep = match matches.value_of("separator") {
		Some(f) => f,
        None => panic!("provide --separator option"),
	};
	let separator = match sep {
		"\\t" => "\t",
		_ => sep,
	};
	let column = match matches.value_of("column") {
		Some(f) => f,
        None => panic!("provide --column option"),
	};
    let timing_index: usize = match usize::from_str(column) {
		Ok(f) => f,
		Err(_) => panic!("--column must be integer"),
	};

	let file_path = matches.value_of("input").unwrap();

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

    let f = match File::open(&path) {
        Err(why) => panic!("couldn't open {}: {}", display, why.description()),
        Ok(f) => f,
    };

	let file = BufReader::with_capacity(128 * 1024, &f);

    let mut timings: Vec<f32> = vec![];

	for line in file.lines().filter_map(|result| result.ok()) {
		let v: Vec<&str> = line.split(separator).collect();
        let t = v[timing_index];

        if t == "-" {
            continue;
        }
		if matches.is_present("print") {
			println!("{}", line);
		}
        match f32::from_str(&t) {
            Err(_) => continue,
            Ok(f) => timings.push(f),
        };
    }

    print_results(timings);
}
<commit_msg>Some formatting<commit_after>extern crate clap;

use clap::{Arg, App};
use std::cmp::Ordering::Equal;
use std::error::Error;
use std::f32;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::path::Path;
use std::str::FromStr;

fn print_results(mut timings: Vec<f32>) {
    timings.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Equal));

    let start: usize = 0;
    let end: usize = timings.len() - 1;
    if end == 0 {
        return;
    };
    let median: usize = end / 2;
    let p70: usize = end.checked_mul(70).unwrap().checked_div(100).unwrap();
    let p80: usize = end.checked_mul(80).unwrap().checked_div(100).unwrap();
    let p95: usize = end.checked_mul(95).unwrap().checked_div(100).unwrap();
    let p99: usize = end.checked_mul(99).unwrap().checked_div(100).unwrap();

    println!("");
    println!("Results:");
    println!("    Total count:   {}", timings.len());
    println!("    Min:           {}", timings[start]);
    println!("    Median:        {} ({})", timings[median], median + 1);
    println!("    70 percentile: {} ({})", timings[p70], p70 + 1);
    println!("    80 percentile: {} ({})", timings[p80], p80 + 1);
    println!("    95 percentile: {} ({})", timings[p95], p95 + 1);
    println!("    99 percentile: {} ({})", timings[p99], p99 + 1);
    println!("    Max:           {} ({})", timings[end], end + 1);
}

// This is the main function
fn main() {

    let matches = App::new("Calculate response time percentiles on logs")
        .version("0.0.1")
        .author("Vladimir Prokhoda <vladimirbright@gmail.com>")
        .arg(Arg::with_name("separator")
            .short("s")
            .long("separator")
            .default_value(" ")
            .help("Column separator")
            .takes_value(true))
        .arg(Arg::with_name("column")
            .short("c")
            .long("column")
            .default_value("8")
            .use_delimiter(false)
            .help("Column number to use")
            .takes_value(true))
        .arg(Arg::with_name("input")
            .help("Sets the input file to use")
            .required(true)
            .index(1))
            .arg(Arg::with_name("print")
            .short("p")
            .long("print")
            .help("Print matched rows"))
        .get_matches();

    let sep = match matches.value_of("separator") {
        Some(f) => f,
        None => panic!("provide --separator option"),
    };
    let separator = match sep {
        "\\t" => "\t",
        _ => sep,
    };
    let column = match matches.value_of("column") {
        Some(f) => f,
        None => panic!("provide --column option"),
    };
    let timing_index: usize = match usize::from_str(column) {
        Ok(f) => f,
        Err(_) => panic!("--column must be integer"),
    };

	let file_path = matches.value_of("input").unwrap();
    let path = Path::new(&file_path);
    let display = path.display();

    let f = match File::open(&path) {
        Err(why) => panic!("couldn't open {}: {}", display, why.description()),
        Ok(f) => f,
    };
	let file = BufReader::with_capacity(128 * 1024, &f);

    let mut timings: Vec<f32> = vec![];

	for line in file.lines().filter_map(|result| result.ok()) {
		let v: Vec<&str> = line.split(separator).collect();
        let t = v[timing_index];

        if t == "-" {
            continue;
        }
		if matches.is_present("print") {
			println!("{}", line);
		}
        match f32::from_str(&t) {
            Err(_) => continue,
            Ok(f) => timings.push(f),
        };
    }

    print_results(timings);
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate futures;
extern crate tokio_proto;
extern crate tokio_service;
extern crate tokio_core;
extern crate tokio_io;
extern crate bytes;

use std::str;
use std::io;
//use std::io::{self, ErrorKind, Write};
use std::net::ToSocketAddrs;

use futures::{future, Future, BoxFuture};
use futures::{Stream, Poll, Async};
use futures::{Sink, AsyncSink, StartSend};
use tokio_proto::TcpClient;
use tokio_proto::pipeline::ClientProto;
use tokio_service::Service;
use tokio_core::reactor::Core;
use tokio_io::codec::{Encoder, Decoder};
use tokio_io::codec::Framed;
use tokio_io::{AsyncRead, AsyncWrite};
//use bytes::{BytesMut, BufMut};
use bytes::BytesMut;

#[derive(Default)]
pub struct LineCodec;

impl Decoder for LineCodec {
    type Item = String;
    type Error = io::Error;

    fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<String>> {
        if let Some(i) = buf.iter().position(|&b| b == b'\n') {
            let line = buf.split_to(i);
            buf.split_to(1);
            match str::from_utf8(&line) {
                Ok(s) => Ok(Some(s.to_string())),
                Err(_) => Err(io::Error::new(io::ErrorKind::Other,
                                             "invalid UTF-8")),
            }
        } else {
            Ok(None)
        }
    }
}

impl Encoder for LineCodec {
    type Item = String;
    type Error = io::Error;

    fn encode(&mut self, msg: String, buf: &mut BytesMut) -> io::Result<()> {
        buf.extend(msg.as_bytes());
        buf.extend(b"\n");
        Ok(())
    }
}

pub struct PingPong<T> {
    upstream: T,
    server: Option<String>,
}

impl<T> PingPong<T> {
    fn new(t: T) -> Self {
        PingPong {
            upstream: t,
            server: None,
        }
    }
}

impl<T> Stream for PingPong<T>
    where T: Stream<Item = String, Error = io::Error>,
          T: Sink<SinkItem = String, SinkError = io::Error>,
{
    type Item = String;
    type Error = io::Error;

    fn poll(&mut self) -> Poll<Option<String>, io::Error> {
        // Poll the upstream transport
        match try_ready!(self.upstream.poll()) {
            Some(ref msg) if msg.starts_with("PING ") => {
                // Intercept pings
                self.server = Some(msg[5..].to_owned());
                // what's this ↓ ?
                // Try flushing the pong, only bubble up errors
                Ok(Async::Ready(try_ready!(self.upstream.poll())))
            }
            m => Ok(Async::Ready(m))
        }
    }
}

impl<T> Sink for PingPong<T>
    where T: Sink<SinkItem = String, SinkError = io::Error>,
{
    type SinkItem = String;
    type SinkError = io::Error;

    fn start_send(&mut self, item: String) -> StartSend<String, io::Error> {
        // Only accept the write if there are no pending pongs
        if self.server.is_some() {
            return Ok(AsyncSink::NotReady(item));
        }
        self.upstream.start_send(item)
    }

    fn poll_complete(&mut self) -> Poll<(), io::Error> {
        if let Some(s) = self.server.take() {
            let pong = format!("PONG {}", s);
            self.upstream.start_send(pong)?;
        }
        self.upstream.poll_complete()
    }
}

pub struct LineProto;

impl<T: AsyncRead + AsyncWrite + 'static> ClientProto<T> for LineProto {
    type Request = String;
    type Response = String;
    type Transport = PingPong<Framed<T, LineCodec>>;
    type BindTransport = Result<Self::Transport, io::Error>;
    fn bind_transport(&self, io: T) -> Self::BindTransport {
        Ok(PingPong::new(io.framed(LineCodec)))
    }
}

pub struct Echo;

impl Service for Echo {
    type Request = String;
    type Response = String;
    type Error = io::Error;
    type Future = BoxFuture<Self::Response, Self::Error>;

    fn call(&self, req: Self::Request) -> Self::Future {
        future::ok(req).boxed()
    }
}

fn main() {
    //let addr = "0.0.0.0:12345".parse().unwrap();
    //TcpServer::new(IntProto, addr)
    //    .serve(|| Ok(Doubler));
    let cm = "USER qj 0 * qjkx\r\nNICK qjk\r\nJOIN #test\r\n".to_string();

    //let addr = "irc.freenode.org:6697".to_socket_addrs().unwrap().next().unwrap();
    let addr = "0.0.0.0:12345".to_socket_addrs().unwrap().next().unwrap();

    //LineCodec.poll();
    //let lc = LineCodec;
    //lc.AsyncReadframed(LineCodec);

    let mut core = Core::new().unwrap();
    let handle = core.handle();
    //let cli = TcpClient::new(IntProto);
    let cli = TcpClient::new(LineProto);
    let socket = cli.connect(&addr, &handle);
    //let response = socket.and_then(|x| x.call(420));
    let response = socket.and_then(|x| x.call(cm));

    let data = core.run(response);//.unwrap();
    println!("{:?}", data);
    //let (_socket, data) = core.run(response).unwrap();
    //println!("{}", String::from_utf8_lossy(&data));
}

<commit_msg>Need persistent client; fiddling with future::loop_fn<commit_after>#[macro_use]
extern crate futures;
extern crate tokio_proto;
extern crate tokio_service;
extern crate tokio_core;
extern crate tokio_io;
extern crate bytes;

use std::{io, str};
use std::net::ToSocketAddrs;

use futures::{Future, Stream, Poll, Async};
use futures::{Sink, AsyncSink, StartSend};
use futures::future::{self, loop_fn, FutureResult, Loop, LoopFn};
use tokio_proto::TcpClient;
use tokio_proto::pipeline::ClientProto;
use tokio_service::Service;
use tokio_core::reactor::Core;
use tokio_io::codec::{Encoder, Decoder, Framed};
use tokio_io::{AsyncRead, AsyncWrite};
use bytes::BytesMut;

#[derive(Default)]
pub struct LineCodec;

impl Decoder for LineCodec {
    type Item = String;
    type Error = io::Error;

    fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<String>> {
        println!("Received: {:?}", std::str::from_utf8(buf));
        if let Some(i) = buf.iter().position(|&b| b == b'\n') {
            let line = buf.split_to(i);
            buf.split_to(1);
            match str::from_utf8(&line) {
                Ok(s) => Ok(Some(s.to_string())),
                Err(_) => Err(io::Error::new(io::ErrorKind::Other,
                                             "invalid UTF-8")),
            }
        } else {
            Ok(None)
        }
    }
}

impl Encoder for LineCodec {
    type Item = String;
    type Error = io::Error;

    fn encode(&mut self, msg: String, buf: &mut BytesMut) -> io::Result<()> {
        //println!("Sending: {}", msg);
        buf.extend(msg.as_bytes());
        buf.extend(b"\n");
        Ok(())
    }
}

pub struct PingPong<T> {
    upstream: T,
    response: Option<String>,
    // TODO: stuff like AWAY ?
}

impl<T> PingPong<T> {
    fn new(t: T) -> Self {
        PingPong {
            upstream: t,
            response: None,
        }
    }
}

impl<T> Stream for PingPong<T>
    where T: Stream<Item = String, Error = io::Error>,
          T: Sink<SinkItem = String, SinkError = io::Error>,
{
    type Item = String;
    type Error = io::Error;

    fn poll(&mut self) -> Poll<Option<String>, io::Error> {
        //loop {
        // Poll the upstream transport
        match try_ready!(self.upstream.poll()) {
            Some(ref msg) if msg.starts_with("PING ") => {
                // Intercept pings
                let resp = msg.replacen("PING", "PONG", 1);
                self.response = Some(resp);
                self.poll_complete()?;
                let _poll = try_ready!(self.upstream.poll());
                // Never hit:
                println!("UHHHH `{:?}`", _poll);
                unimplemented!();
                //Ok(Async::Ready(_poll))
            },
            // Final output:
            m => Ok(Async::Ready(m))
                //m => {
            //    self.upstream.foo();
            //},
        }
        //}
    }
}

impl<T> Sink for PingPong<T>
    where T: Sink<SinkItem = String, SinkError = io::Error>,
{
    type SinkItem = String;
    type SinkError = io::Error;

    fn start_send(&mut self, item: String) -> StartSend<String, io::Error> {
        // Only accept the write if there are no pending pongs
        if self.response.is_some() {
            return Ok(AsyncSink::NotReady(item));
        }
        self.upstream.start_send(item)
    }

    fn poll_complete(&mut self) -> Poll<(), io::Error> {
        if let Some(pong) = self.response.take() {
            self.upstream.start_send(pong)?;
        }
        self.upstream.poll_complete()
    }
}

pub struct LineProto;

impl<T: AsyncRead + AsyncWrite + 'static> ClientProto<T> for LineProto {
    type Request = String;
    type Response = String;
    type Transport = PingPong<Framed<T, LineCodec>>;
    type BindTransport = Result<Self::Transport, io::Error>;
    fn bind_transport(&self, io: T) -> Self::BindTransport {
        Ok(PingPong::new(io.framed(LineCodec)))
    }
}

fn main() {
    //let addr = "0.0.0.0:12345".parse().unwrap();
    //TcpServer::new(IntProto, addr)
    //    .serve(|| Ok(Doubler));
    let cm = "USER qj 0 * qjkx\r\nNICK qjk\r\nJOIN #test\r\n".to_string();

    //let addr = "irc.freenode.org:6697".to_socket_addrs().unwrap().next().unwrap();
    let addr = "0.0.0.0:12345".to_socket_addrs().unwrap().next().unwrap();

    let mut core = Core::new().unwrap();
    let handle = core.handle();

    let tc = TcpClient::new(LineProto);
    let response = tc
        .connect(&addr, &handle)
        .and_then(|client| {
            client.call("ONE\r\n".to_string())
                .and_then(move |response| {
                    client.call("TWO".to_string())
                        .and_then(move |response| {
                            client.call("THREE".to_string())
                        })
                })
        });
    //let socket = tc.connect(&addr, &handle);
    //let response = socket.and_then(|x| x.call(cm));
    let data = core.run(response);//.unwrap();
    println!("{:?}", data);

    //let client = tc.and_then(|x| x.call(cm));
    //response.foo();
    //response.and_then(|x| future::ok(x).foo());
                    //client.call("BYE\r\n".to_string())

    //let listen = cli.connect(&addr, &handle).and_then(|r| { });
    //let listen = loop_fn(cli.connect(&addr, &handle), |msg| {
    //let listen = loop_fn(socket.and_then(|s| s.call(cm)), |msg| {

    //let listen: LoopFn<Result<_,io::Error>, _> = loop_fn(conn, |cli| {
        //cli.and_then(|x| cli.call());
        //cli.foo();
        //cli.and_then(|x| future::ok(x).foo())
        //let r = cli.and_then(|x| x.call("HI\r\n"));
        //let r = cli.call("HI\r\n");
        //r.foo();
    //});

    //let mut a = core.run(listen).unwrap();
    //a.poll();
        /*
    let listen: LoopFn<Result<_,io::Error>, _> = loop_fn(response, |x| {
        k
        x.and_then(|y| {
            //y.call("BAD\r\n").and_then(
            if y.contains("fuck") {
                //Ok(Loop::Break(y))
                Ok(Loop::Break(x))
            } else {
                Ok(Loop::Continue(x))
            }
        })
        */
    /*
        if true {
            Ok(Loop::Break(msg))
        } else {
            //Ok(Loop::Continue(msg))
            Ok(Loop::Break(msg))
        }
    });
        */

        /*
    let req = cli.connect(&addr, &handle)
        .and_then(|client| {
            client.call(cm)
                .and_then(move |response| {
                    println!("CLIENT: {:?}", response);
                    client.call("BYE\r\n".to_string())
                })
            .and_then(|response| {
                println!("CLIENT_: {:?}", response);
                Ok(())
            })
        });
        */

    //let response = socket.and_then(|x| x.call(cm));

    //let data = core.run(response);//.unwrap();
    //println!("{:?}", data);
    //let (_socket, data) = core.run(response).unwrap();
    //println!("{}", String::from_utf8_lossy(&data));
}
<|endoftext|>


<commit_before>extern crate image;
extern crate dungenon;

use std::io;
use dungenon::level::Level;
use dungenon::tile::Tile;
use dungenon::generator::MazeGen;

use image::png::PNGEncoder;

fn main() {
    println!("Initializing level...");
    let mut level = init_level();

    println!("This is the dungenon-drawer. \nType Help for list of commands.");
    loop {
        println!("Enter command:");
        let mut command = String::new();
        io::stdin().read_line(&mut command)
        .expect("Failed to read line");
        println!("{}", command);
        match command.trim() {
            "dungeon" => carve_dungeon(&mut level),
            "maze" => carve_maze(&mut level),
            "room" => carve_rooms(&mut level),
            "reset" => level = init_level(),
            "export" => {
                png_export(&mut level);
            },
            "help" => {
                println!("Help command is not implemented yet!");
            }
            "exit" => break,
            _ => println!("Invalid command"),
        }
    }
}

fn init_level() -> Level {
    println!("Input level width: ");
    let mut x = usize_from_cmd();

    println!("Input level height: ");
    let mut y = usize_from_cmd();

    Level::new_filled_with(Some(Tile::Wall), x, y)
}

fn carve_dungeon(level: &mut Level) {
    println!("Not implemented.");
}

fn carve_maze(level: &mut Level) {
    println!("Input MazeGen startpos x coordinate: ");
    let mut x = usize_from_cmd();

    println!("Input MazeGen startpos y coordinate: ");
    let mut y = usize_from_cmd();
    let mut mazegen = MazeGen::new(x,y);
    level.apply(|m| mazegen.generate(m));
}

fn carve_rooms(level: &mut Level) {
    println!("Not implemented.");
}

fn png_export(level: &mut Level) {

}

fn print_level(level: &mut Level) {
    let mut string = String::new();
    for y in 0 .. level.get_width() {
        for x in 0 .. level.get_height() {
            match level[(x,y)] {
                Some(tile) => {
                    match tile {
                        Tile::
                    }
                },
                None => string.push(' ');
            }
        }
        string.push('\n');
    }
}

fn usize_from_cmd() -> usize {
    let mut num = String::new();

    io::stdin().read_line(&mut num)
        .expect("failed to read line");

    let num: usize = num.trim().parse()
        .expect("Please type a number!");
    num
}
<commit_msg>level printing works.<commit_after>extern crate image;
extern crate dungenon;

use std::io;
use dungenon::level::Level;
use dungenon::tile::Tile;
use dungenon::generator::MazeGen;

use image::png::PNGEncoder;

fn main() {
    println!("Initializing level...");
    let mut level = init_level();

    println!("This is the dungenon-drawer. \nType Help for list of commands.");
    loop {
        println!("Enter command:");
        let mut command = String::new();
        io::stdin().read_line(&mut command)
        .expect("Failed to read line");
        println!("{}", command);
        match command.trim() {
            "dungeon" => carve_dungeon(&mut level),
            "maze" => carve_maze(&mut level),
            "room" => carve_rooms(&mut level),
            "reset" => level = init_level(),
            "export" => {
                png_export(&mut level);
            },
            "print" => print_level(&level),
            "help" => {
                println!("Help command is not implemented yet!");
            }
            "exit" => break,
            _ => println!("Invalid command"),
        }
    }
}

fn init_level() -> Level {
    println!("Input level width: ");
    let mut x = usize_from_cmd();

    println!("Input level height: ");
    let mut y = usize_from_cmd();

    Level::new_filled_with(Some(Tile::Wall), x, y)
}

fn carve_dungeon(level: &mut Level) {
    println!("Not implemented.");
}

fn carve_maze(level: &mut Level) {
    println!("Input MazeGen startpos x coordinate: ");
    let mut x = usize_from_cmd();

    println!("Input MazeGen startpos y coordinate: ");
    let mut y = usize_from_cmd();
    let mut mazegen = MazeGen::new(x,y);
    level.apply(|m| mazegen.generate(m));
}

fn carve_rooms(level: &mut Level) {
    println!("Not implemented.");
}

fn png_export(level: &mut Level) {

}

fn print_level(level: &Level) {
    let mut string = String::new();
    for y in 0 .. level.get_width() {
        for x in 0 .. level.get_height() {
            match level[(x,y)] {
                Some(ref tile) => {
                    match tile {
                        &Tile::Wall => string.push('#'),
                        &Tile::Floor => string.push(' '),
                        &Tile::Void => string.push('*'),
                    }
                },
                None => string.push('*'),
            }
        }
        string.push('\n');
    }
    println!("{}", &string);
}

fn usize_from_cmd() -> usize {
    let mut num = String::new();

    io::stdin().read_line(&mut num)
        .expect("failed to read line");

    let num: usize = num.trim().parse()
        .expect("Please type a number!");
    num
}
<|endoftext|>


<commit_before>use std::env;
use std::fs::File;
use std::io;
use std::io::prelude::*;

fn run(source: &String) -> i32 {
    unimplemented!();
}

fn run_file(file_name: &String) -> i32 {
    match File::open(file_name) {
        Err(e) => {
            println!("Error opening {}", file_name);
            1
        }
        Ok(mut file) => {
            let mut source = String::new();
            match file.read_to_string(&mut source) {
                Err(e) => {
                    println!("Error reading {}", file_name);
                    1
                }
                Ok(_) => run(&source),
            }
        }
    }
}

fn run_prompt() -> i32 {
    loop {
        print!("> ");
        io::stdout().flush();
        let mut source = String::new();
        match io::stdin().read_line(&mut source) {
            Ok(n) => {
                run(&source);
                ()
            }
            Err(e) => (),
        }
    }
    0
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let exit_code = match args.len() {
        // The first argument is the program name
        1 => run_prompt(),
        2 => run_file(&args[1]),
        _ => {
            println!("Usage: rulox [script]");
            1
        }
    };
    std::process::exit(exit_code)
}
<commit_msg>Tokenize stub<commit_after>use std::env;
use std::fs::File;
use std::io;
use std::io::prelude::*;

#[derive(Debug)]
struct Token {
}

fn tokenize(source: &String) -> Vec<Token> {
    unimplemented!();
}

fn run(source: &String) -> i32 {
    let tokens = tokenize(source);
    for token in tokens {
        println!("{:?}", token);
    }
    0
}

fn run_file(file_name: &String) -> i32 {
    match File::open(file_name) {
        Err(e) => {
            println!("Error opening {}", file_name);
            1
        }
        Ok(mut file) => {
            let mut source = String::new();
            match file.read_to_string(&mut source) {
                Err(e) => {
                    println!("Error reading {}", file_name);
                    1
                }
                Ok(_) => run(&source),
            }
        }
    }
}

fn run_prompt() -> i32 {
    loop {
        print!("> ");
        io::stdout().flush();
        let mut source = String::new();
        match io::stdin().read_line(&mut source) {
            Ok(n) => {
                run(&source);
                ()
            }
            Err(e) => (),
        }
    }
    0
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let exit_code = match args.len() {
        // The first argument is the program name
        1 => run_prompt(),
        2 => run_file(&args[1]),
        _ => {
            println!("Usage: rulox [script]");
            1
        }
    };
    std::process::exit(exit_code)
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate clap;
extern crate chrono;
#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;

use std::env;
use std::fs;
use std::fs::{OpenOptions, File, Metadata};
use std::io::{Write, ErrorKind};
use std::path::PathBuf;

use chrono::{DateTime, Utc};
use clap::{App, Arg, AppSettings, SubCommand};
use serde_json::Error;

#[derive(Serialize, Deserialize, Debug, Clone)]
struct Period {
    project: String,
    start_time: DateTime<Utc>,
    end_time: Option<DateTime<Utc>>,
}

fn main() {
    let matches = App::new("Doug")
        .version(crate_version!())
        .about("A time tracking command-line utility")
        .author(crate_authors!())
        .settings(&[
            AppSettings::DeriveDisplayOrder,
            AppSettings::GlobalVersion,
            AppSettings::SubcommandRequiredElseHelp,
            AppSettings::VersionlessSubcommands,
            AppSettings::DisableHelpSubcommand,
        ])
        .subcommand(SubCommand::with_name("start")
            .about("Track new or existing project")
            .arg(Arg::with_name("project")
                .help("project to track")
                .required(true)))
        .subcommand(SubCommand::with_name("status")
            .about("Display elapsed time, start time, and running project name"))
        .subcommand(SubCommand::with_name("stop")
            .about("Stop any running projects"))
        .subcommand(SubCommand::with_name("cancel")
            .about("Stop running project and remove most recent time interval"))
        .subcommand(SubCommand::with_name("restart")
            .about("Track last running project"))
        .subcommand(SubCommand::with_name("log")
            .about("Display time intervals across all projects"))
        .subcommand(SubCommand::with_name("report")
            .about("Display aggregate time from last week"))
        .subcommand(SubCommand::with_name("amend")
            .about("Change name of currently running project")
            .arg(Arg::with_name("project")
                .help("new project name")
                .required(true)))
        .subcommand(SubCommand::with_name("edit")
            .about("Edit last frame or currently running frame")
            .arg(Arg::with_name("project")
                .help("project to track")))
        .get_matches();

    if let Some(matches) = matches.subcommand_matches("start") {
        if matches.is_present("project") {
            start(matches.value_of("project").unwrap());
        }
    }

    if let Some(matches) = matches.subcommand_matches("amend") {
        if matches.is_present("project") {
            amend(matches.value_of("project").expect("missing project name"));
        }
    }

    if let Some(matches) = matches.subcommand_matches("edit") {
        edit(matches.value_of("project"));
    }

    match matches.subcommand_name() {
        Some("stop") => stop(),
        Some("cancel") => cancel(),
        Some("restart") => restart(),
        Some("log") => log(),
        Some("report") => report(),
        _ => {},
    }
}

fn start(project_name: &str) {

    let mut periods = get_periods();

    if !periods.is_empty() {
        let last_index = periods.len() - 1;
        if let Some(period) = periods.get_mut(last_index) {
            if period.end_time.is_none() {
                eprintln!("Sorry, you're currently tracking project `{}`, started `{}`", period.project, period.start_time);
                eprintln!("Try stopping your current project with `stop` first.`");
                return
            }
        }
    }
    let current_period = create_period(project_name);
    // store current period in file
    print!("Started tracking project '{}'", current_period.project);
    periods.push(current_period);
    save_periods(periods.to_vec());
}

fn stop() {
    let mut periods = get_periods();
    let mut updated_period = false;
    let last_index = periods.len() - 1;
    if periods.len() > 0 {
        if let Some(period) = periods.get_mut(last_index) {
            if period.end_time.is_none() {
                period.end_time = Some(Utc::now());
                updated_period = true;
                println!("Stopped project: `{}`", period.project);
            }
        }
    }
    if updated_period {
        save_periods(periods);
    } else {
        eprintln!("No running project to stop.");
    }
}

fn cancel() {
    let mut periods = get_periods();
    if let Some(period) = periods.pop() {
        if period.end_time.is_none() {
            save_periods(periods);
            println!("Canceled running project: {}", period.project);
            return
        }
    }
    eprintln!("No running project to cancel");
}

fn restart() {
    unimplemented!();
}

fn log() {
    unimplemented!();
}

fn report() {
    unimplemented!();
}

fn amend(project_name: &str) {
    println!("Amend project: {:?}", project_name);
    unimplemented!();
}

fn edit(project_name: Option<&str>) {
    if let Some(name) = project_name {
        println!("Edit project: {}", name);
    }
    unimplemented!();
}

fn create_period(project: &str) -> Period {
    Period {
        project: String::from(project),
        start_time: Utc::now(),
        end_time: None,
    }
}

fn get_config_folder() -> PathBuf {
    let home_dir = env::var("HOME").expect("Failed to find home directory from environment 'HOME'");
    let mut config_folder = PathBuf::from(home_dir);
    config_folder.push(".config/doug");
    config_folder
}

fn get_config_file_path() -> PathBuf {
    let config_folder = get_config_folder();
    let mut config_file = PathBuf::from(&config_folder);
    config_file.push("periods.json");
    config_file
}

fn get_config_back_file_path() -> PathBuf {
    let config_file = get_config_file_path();
    config_file.with_extension("json-backup")
}

fn get_config_file() -> (File, Metadata) {
    let config_file = get_config_file_path();
    (OpenOptions::new()
        .create(true)
        .read(true)
        .write(true)
        .open(&config_file)
        .expect(&format!("Couldn't open datafile: {:?}", config_file)),
    fs::metadata(&config_file).expect("Couldn't access datafile metadata")
    )
}

fn get_periods() -> Vec<Period> {
    let config_folder = get_config_folder();

    match fs::create_dir(&config_folder) {
        Err(ref error) if error.kind() == ErrorKind::AlreadyExists => {},
        Err(error) => panic!("There was a problem creating the config directory: {:?}", error),
        Ok(_) => {},
    }

    let (file, metadata) = get_config_file();

    let periods: Result<Vec<Period>, Error> = serde_json::from_reader(file);
    let periods = match periods {
        Ok(p) => p,
        Err(_) if metadata.len() > 0 => Vec::new(),
        Err(error) => panic!("There was a serialization issue: {:?}", error),
    };
    periods
}

fn save_periods(periods: Vec<Period>) {
    let serialized = serde_json::to_string(&periods).expect("Couldn't serialize data to string");
    let config_file = get_config_file_path();
    let config_file_backup = get_config_back_file_path();
    fs::copy(&config_file, &config_file_backup).expect("Couldn't create backup file");
    let mut file = OpenOptions::new()
                    .create(true)
                    .write(true)
                    .truncate(true)
                    .open(&config_file)
                    .expect("Couldn't open file for saving period.");

    file.write_all(serialized.as_bytes()).expect("Couldn't write serialized data to file");
}
<commit_msg>Add restart subcommand (#4)<commit_after>#[macro_use]
extern crate clap;
extern crate chrono;
#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;

use std::env;
use std::fs;
use std::fs::{OpenOptions, File, Metadata};
use std::io::{Write, ErrorKind};
use std::path::PathBuf;

use chrono::{DateTime, Utc};
use clap::{App, Arg, AppSettings, SubCommand};
use serde_json::Error;

#[derive(Serialize, Deserialize, Debug, Clone)]
struct Period {
    project: String,
    start_time: DateTime<Utc>,
    end_time: Option<DateTime<Utc>>,
}

fn main() {
    let matches = App::new("Doug")
        .version(crate_version!())
        .about("A time tracking command-line utility")
        .author(crate_authors!())
        .settings(&[
            AppSettings::DeriveDisplayOrder,
            AppSettings::GlobalVersion,
            AppSettings::SubcommandRequiredElseHelp,
            AppSettings::VersionlessSubcommands,
            AppSettings::DisableHelpSubcommand,
        ])
        .subcommand(SubCommand::with_name("start")
            .about("Track new or existing project")
            .arg(Arg::with_name("project")
                .help("project to track")
                .required(true)))
        .subcommand(SubCommand::with_name("status")
            .about("Display elapsed time, start time, and running project name"))
        .subcommand(SubCommand::with_name("stop")
            .about("Stop any running projects"))
        .subcommand(SubCommand::with_name("cancel")
            .about("Stop running project and remove most recent time interval"))
        .subcommand(SubCommand::with_name("restart")
            .about("Track last running project"))
        .subcommand(SubCommand::with_name("log")
            .about("Display time intervals across all projects"))
        .subcommand(SubCommand::with_name("report")
            .about("Display aggregate time from last week"))
        .subcommand(SubCommand::with_name("amend")
            .about("Change name of currently running project")
            .arg(Arg::with_name("project")
                .help("new project name")
                .required(true)))
        .subcommand(SubCommand::with_name("edit")
            .about("Edit last frame or currently running frame")
            .arg(Arg::with_name("project")
                .help("project to track")))
        .get_matches();

    if let Some(matches) = matches.subcommand_matches("start") {
        if matches.is_present("project") {
            start(matches.value_of("project").unwrap());
        }
    }

    if let Some(matches) = matches.subcommand_matches("amend") {
        if matches.is_present("project") {
            amend(matches.value_of("project").expect("missing project name"));
        }
    }

    if let Some(matches) = matches.subcommand_matches("edit") {
        edit(matches.value_of("project"));
    }

    match matches.subcommand_name() {
        Some("stop") => stop(),
        Some("cancel") => cancel(),
        Some("restart") => restart(),
        Some("log") => log(),
        Some("report") => report(),
        _ => {},
    }
}

fn start(project_name: &str) {

    let mut periods = get_periods();

    if !periods.is_empty() {
        let last_index = periods.len() - 1;
        if let Some(period) = periods.get_mut(last_index) {
            if period.end_time.is_none() {
                eprintln!("Sorry, you're currently tracking project `{}`, started `{}`", period.project, period.start_time);
                eprintln!("Try stopping your current project with `stop` first.`");
                return
            }
        }
    }
    let current_period = create_period(project_name);
    // store current period in file
    print!("Started tracking project '{}'", current_period.project);
    periods.push(current_period);
    save_periods(periods.to_vec());
}

fn stop() {
    let mut periods = get_periods();
    let mut updated_period = false;
    let last_index = periods.len() - 1;
    if periods.len() > 0 {
        if let Some(period) = periods.get_mut(last_index) {
            if period.end_time.is_none() {
                period.end_time = Some(Utc::now());
                updated_period = true;
                println!("Stopped project: `{}`", period.project);
            }
        }
    }
    if updated_period {
        save_periods(periods);
    } else {
        eprintln!("No running project to stop.");
    }
}

fn cancel() {
    let mut periods = get_periods();
    if let Some(period) = periods.pop() {
        if period.end_time.is_none() {
            save_periods(periods);
            println!("Canceled running project: {}", period.project);
            return
        }
    }
    eprintln!("No running project to cancel");
}

fn restart() {
    let periods = get_periods();
    let mut new_periods = periods.to_vec();
    if let Some(period) = periods.last() {
        if !period.end_time.is_none() {
            let new_period = create_period(&period.project);
            new_periods.push(new_period);
            save_periods(new_periods);
            println!("Tracking last running project: {}", period.project);
            return
        }
    }
    eprintln!("No running project to cancel");
}

fn log() {
    unimplemented!();
}

fn report() {
    unimplemented!();
}

fn amend(project_name: &str) {
    println!("Amend project: {:?}", project_name);
    unimplemented!();
}

fn edit(project_name: Option<&str>) {
    if let Some(name) = project_name {
        println!("Edit project: {}", name);
    }
    unimplemented!();
}

fn create_period(project: &str) -> Period {
    Period {
        project: String::from(project),
        start_time: Utc::now(),
        end_time: None,
    }
}

fn get_config_folder() -> PathBuf {
    let home_dir = env::var("HOME").expect("Failed to find home directory from environment 'HOME'");
    let mut config_folder = PathBuf::from(home_dir);
    config_folder.push(".config/doug");
    config_folder
}

fn get_config_file_path() -> PathBuf {
    let config_folder = get_config_folder();
    let mut config_file = PathBuf::from(&config_folder);
    config_file.push("periods.json");
    config_file
}

fn get_config_back_file_path() -> PathBuf {
    let config_file = get_config_file_path();
    config_file.with_extension("json-backup")
}

fn get_config_file() -> (File, Metadata) {
    let config_file = get_config_file_path();
    (OpenOptions::new()
        .create(true)
        .read(true)
        .write(true)
        .open(&config_file)
        .expect(&format!("Couldn't open datafile: {:?}", config_file)),
    fs::metadata(&config_file).expect("Couldn't access datafile metadata")
    )
}

fn get_periods() -> Vec<Period> {
    let config_folder = get_config_folder();

    match fs::create_dir(&config_folder) {
        Err(ref error) if error.kind() == ErrorKind::AlreadyExists => {},
        Err(error) => panic!("There was a problem creating the config directory: {:?}", error),
        Ok(_) => {},
    }

    let (file, metadata) = get_config_file();

    let periods: Result<Vec<Period>, Error> = serde_json::from_reader(file);
    let periods = match periods {
        Ok(p) => p,
        Err(_) if metadata.len() > 0 => Vec::new(),
        Err(error) => panic!("There was a serialization issue: {:?}", error),
    };
    periods
}

fn save_periods(periods: Vec<Period>) {
    let serialized = serde_json::to_string(&periods).expect("Couldn't serialize data to string");
    let config_file = get_config_file_path();
    let config_file_backup = get_config_back_file_path();
    fs::copy(&config_file, &config_file_backup).expect("Couldn't create backup file");
    let mut file = OpenOptions::new()
                    .create(true)
                    .write(true)
                    .truncate(true)
                    .open(&config_file)
                    .expect("Couldn't open file for saving period.");

    file.write_all(serialized.as_bytes()).expect("Couldn't write serialized data to file");
}
<|endoftext|>