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

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

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

mod draw;
use draw::draw;
use draw::get_absolute_draw_indices;
mod find;
use find::FindOptSubset;

extern crate ncurses;
use ncurses::*;

extern crate getopts;
use getopts::Options;

extern crate pest;
#[macro_use]
extern crate pest_derive;

use pest::Parser;

#[derive(Parser)]
#[grammar = "cmd.pest"]
struct IdentParser;

extern crate memmem;
use memmem::{Searcher, TwoWaySearcher};

#[derive(PartialEq, Copy, Clone)]
pub enum CursorSelects {
    LeftNibble,
    RightNibble,
    AsciiChar,
}

#[derive(Copy, Clone)]
pub struct CursorState {
    pos: usize,
    sel: CursorSelects,
}

fn main() {
    const VERSION: &str = env!("CARGO_PKG_VERSION");
    let mut buf = vec![];
    let mut cursor = CursorState {
        pos: 0,
        sel: CursorSelects::LeftNibble,
    };
    // 0 = display data from first line of file
    let mut screenoffset: usize = 0;
    const COLS: usize = 16;
    let mut command = String::new();
    let mut lastcommand = String::new();
    let mut debug = String::new();

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

    let args: Vec<_> = env::args().collect();
    let program = args[0].clone();
    let mut opts = Options::new();
    opts.optflag("h", "help", "print this help menu");
    opts.optflag("v", "version", "print the version");
    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => {
            endwin();
            println!("{}", f.to_string());
            println!("Usage: {} FILE [options]", program);
            return;
        }
    };
    if matches.opt_present("v") {
        endwin();
        println!("Version: {}", VERSION);
        return;
    }
    if matches.opt_present("h") {
        endwin();
        println!("Usage: {} FILE [options]", program);
        return;
    }

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

    let patharg = match matches.free.is_empty() {
        true => String::new(),
        false => matches.free[0].clone(),
    };
    let path = Path::new(&patharg);

    if patharg.is_empty() {
        endwin();
        println!("Patharg is empty!\n");
        return;
    }

    let mut file = match OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(&path)
    {
        Err(why) => {
            endwin();
            println!("Could not open {}: {}", path.display(), why.to_string());
            return;
        }
        Ok(file) => file,
    };
    file.read_to_end(&mut buf)
        .ok()
        .expect("File could not be read.");

    let draw_range = get_absolute_draw_indices(buf.len(), COLS, screenoffset);
    draw(
        &buf[draw_range.0..draw_range.1],
        COLS,
        &command,
        &mut debug,
        cursor,
        screenoffset,
    );

    let mut quitnow = false;
    let mut autoparse = false;
    while quitnow == false {
        if !autoparse {
            let key = std::char::from_u32(getch() as u32).unwrap();
            command.push_str(&key.clone().to_string());
        }
        autoparse = false;

        let parsethisstring = command.clone();
        let commands = IdentParser::parse(Rule::cmd_list, &parsethisstring)
            .unwrap_or_else(|e| panic!("{}", e));

        let mut clear = true;
        let mut save = false;
        for cmd in commands {
            match cmd.as_rule() {
                Rule::down => {
                    // debug.push_str(&format!("{:?}", cmd.as_rule()));
                    if cursor.pos + COLS < buf.len() {
                        // not at end
                        cursor.pos += COLS;
                    } else {
                        // when at end
                        if buf.len() != 0 {
                            // Suppress underflow
                            cursor.pos = buf.len() - 1;
                        }
                    }
                }
                Rule::up => {
                    if cursor.pos >= COLS {
                        cursor.pos -= COLS;
                    }
                }
                Rule::left => {
                    if cursor.sel == CursorSelects::AsciiChar {
                        if cursor.pos > 0 {
                            cursor.pos -= 1;
                        }
                    } else if cursor.sel == CursorSelects::RightNibble {
                        cursor.sel = CursorSelects::LeftNibble;
                    } else if cursor.sel == CursorSelects::LeftNibble {
                        if cursor.pos > 0 {
                            // not at start
                            cursor.sel = CursorSelects::RightNibble;
                            cursor.pos -= 1;
                        }
                    }
                }
                Rule::right => {
                    if cursor.sel == CursorSelects::AsciiChar {
                        if cursor.pos + 1 < buf.len() {
                            // not at end
                            cursor.pos += 1;
                        }
                    } else if cursor.sel == CursorSelects::LeftNibble {
                        cursor.sel = CursorSelects::RightNibble;
                    } else if cursor.sel == CursorSelects::RightNibble {
                        if cursor.pos + 1 < buf.len() {
                            // not at end
                            cursor.sel = CursorSelects::LeftNibble;
                            cursor.pos += 1;
                        }
                    }
                }
                Rule::start => {
                    cursor.pos -= cursor.pos % COLS; // jump to start of line
                    if cursor.sel == CursorSelects::RightNibble {
                        cursor.sel = CursorSelects::LeftNibble;
                    }
                }
                Rule::end => {
                    // check if no overflow
                    if cursor.pos - (cursor.pos % COLS) + (COLS - 1) < buf.len() {
                        // jump to end of line
                        cursor.pos = cursor.pos - (cursor.pos % COLS) + (COLS - 1);
                    } else {
                        // jump to end of line
                        cursor.pos = buf.len() - 1
                    }
                    if cursor.sel == CursorSelects::LeftNibble {
                        cursor.sel = CursorSelects::RightNibble;
                    }
                }
                Rule::bottom => {
                    cursor.pos = buf.len() - 1;
                    cursor.pos -= cursor.pos % COLS; // jump to start of line
                }
                Rule::replace => {
                    clear = false;
                }
                Rule::replacement => {
                    let key = command.chars().last().unwrap_or('x');
                    if cursor.sel == CursorSelects::AsciiChar {
                        if cursor.pos >= buf.len() {
                            buf.insert(cursor.pos, 0);
                        }
                        buf[cursor.pos] = key as u8;
                    } else {
                        let mask = if cursor.sel == CursorSelects::LeftNibble {
                            0x0F
                        } else {
                            0xF0
                        };
                        let shift = if cursor.sel == CursorSelects::LeftNibble {
                            4
                        } else {
                            0
                        };
                        if cursor.pos >= buf.len() {
                            buf.insert(cursor.pos, 0);
                        }
                        // Change the selected nibble
                        if let Some(c) = key.to_digit(16) {
                            buf[cursor.pos] = buf[cursor.pos] & mask | (c as u8) << shift;
                        }
                    }
                    lastcommand = command.clone();
                    clear = true;
                }
                Rule::replaceend => {
                    clear = true;
                }
                Rule::remove => {
                    // check if in valid range
                    if buf.len() > 0 && cursor.pos < buf.len() {
                        // remove the current char
                        buf.remove(cursor.pos);
                    }
                    // always perform the movement if possible
                    if cursor.pos > 0 && cursor.pos >= buf.len() {
                        cursor.pos -= 1;
                    }
                    lastcommand = command.clone();
                }
                Rule::dd => {
                    let amount: usize = cmd.as_str().parse().unwrap_or(1);
                    // check if in valid range
                    if buf.len() > 0 && cursor.pos < buf.len() {
                        let startofline = cursor.pos - cursor.pos % COLS;
                        let mut endofline = cursor.pos - (cursor.pos % COLS) + (COLS * amount);
                        endofline = cmp::min(endofline, buf.len());
                        buf.drain(startofline..endofline);
                        if buf.len() <= 0 {
                            cursor.pos = 0; // Ensure cursor does not go below 0
                        }
                        if buf.len() > 0 && cursor.pos >= buf.len() {
                            cursor.pos = buf.len() - 1;
                        }
                    }
                    lastcommand = command.clone();
                    clear = true;
                }
                Rule::insert => {
                    // debug.push_str("next chars will be inserted!");
                    clear = false;
                }
                Rule::insertstuff => {
                    let key = command.chars().last().unwrap_or('x');

                    if cursor.sel == CursorSelects::LeftNibble {
                        // Left nibble
                        if let Some(c) = key.to_digit(16) {
                            buf.insert(cursor.pos, (c as u8) << 4);
                            cursor.sel = CursorSelects::RightNibble;
                        }
                    } else if cursor.sel == CursorSelects::RightNibble {
                        // Right nibble
                        if cursor.pos == buf.len() {
                            buf.insert(cursor.pos, 0);
                        }
                        if let Some(c) = key.to_digit(16) {
                            buf[cursor.pos] = buf[cursor.pos] & 0xF0 | c as u8;
                            cursor.sel = CursorSelects::LeftNibble;
                            cursor.pos += 1;
                        }
                    } else if cursor.sel == CursorSelects::AsciiChar {
                        buf.insert(cursor.pos, key as u8);
                        cursor.pos += 1;
                    }

                    clear = false;
                }
                Rule::insertend => {
                    // The reason this does not work is the escape character at the end
                    // lastcommand = command.clone();
                    lastcommand.clear();
                    lastcommand.push_str(&format!(
                        "Command repeation for {:?} not yet implemented.",
                        cmd.as_rule()
                    ));
                    clear = true;
                }
                Rule::jumpascii => {
                    if cursor.sel == CursorSelects::AsciiChar {
                        cursor.sel = CursorSelects::LeftNibble;
                    } else {
                        cursor.sel = CursorSelects::AsciiChar;
                    }
                }
                Rule::helpfile => {
                    command.pop();
                    command.push_str("No helpfile yet");
                    clear = false;
                }
                Rule::repeat => {
                    command = lastcommand.clone();
                    clear = false;
                    autoparse = true;
                }
                Rule::gg => {
                    let linenr: usize = cmd.as_str().parse().unwrap_or(0);
                    cursor.pos = linenr * COLS; // jump to the line
                    if cursor.pos > buf.len() {
                        // detect file end
                        cursor.pos = buf.len();
                    }
                    cursor.pos -= cursor.pos % COLS; // jump to start of line
                    clear = true;
                }
                Rule::backspace => {
                    command.pop();
                    command.pop();
                    clear = false;
                }
                Rule::saveandexit => {
                    save = true;
                    quitnow = true;
                }
                Rule::exit => quitnow = true,
                Rule::save => save = true,
                Rule::escape => (),
                _ => (),
            }

            for inner_cmd in cmd.into_inner() {
                match inner_cmd.as_rule() {
                    Rule::searchstr => {
                        let search = inner_cmd.as_str().as_bytes();
                        let foundpos = TwoWaySearcher::new(&search);
                        cursor.pos = foundpos.search_in(&buf).unwrap_or(cursor.pos);
                    }
                    Rule::searchbytes => {
                        let search = inner_cmd.as_str().as_bytes();
                        let mut needle = vec![];
                        for i in 0..search.len() {
                            let nibble = match search[i] as u8 {
                                c @ 48..=57 => c - 48, // Numbers from 0 to 9
                                b'x' => 0x10,          // x is the wildcard
                                b'X' => 0x10,          // X is the wildcard
                                c @ b'a'..=b'f' => c - 87,
                                c @ b'A'..=b'F' => c - 55,
                                _ => panic!("Should not get to this position!"),
                            };
                            needle.push(nibble);
                        }
                        cursor.pos = buf.find_subset(&needle).unwrap_or(cursor.pos);
                        // debug.push_str(&format!("Searching for: {:?}", needle ));
                    }
                    Rule::gatherone => clear = false,
                    _ => {
                        command.push_str(&format!("no rule for {:?} ", inner_cmd.as_rule()));
                        clear = false;
                    }
                };
            }
            if save {
                if path.exists() {
                    let mut file = match OpenOptions::new()
                        .read(true)
                        .write(true)
                        .create(true)
                        .open(&path)
                    {
                        Err(why) => {
                            panic!("Could not open {}: {}", path.display(), why.to_string())
                        }
                        Ok(file) => file,
                    };
                    file.seek(SeekFrom::Start(0))
                        .ok()
                        .expect("Filepointer could not be set to 0");
                    file.write_all(&mut buf)
                        .ok()
                        .expect("File could not be written.");
                    file.set_len(buf.len() as u64)
                        .ok()
                        .expect("File could not be set to correct lenght.");
                    command.push_str("File saved!");
                } else {
                    command.push_str("Careful, file could not be saved!");
                }
                // TODO: define filename during runtime
                save = false;
            }
            if clear {
                command.clear();
            }

            // Always move screen when cursor leaves screen
            if cursor.pos > (screenheight + screenoffset - 1) * COLS - 1 {
                screenoffset = 2 + cursor.pos / COLS - screenheight;
            }
            if cursor.pos < screenoffset * COLS {
                screenoffset = cursor.pos / COLS;
            }
        }

        let draw_range = get_absolute_draw_indices(buf.len(), COLS, screenoffset);
        draw(
            &buf[draw_range.0..draw_range.1],
            COLS,
            &command,
            &mut debug,
            cursor,
            screenoffset,
        );
    }

    refresh();
    endwin();
}
<commit_msg>Fix autoparse and implement repeation of insert<commit_after>//! # Hexdino
//!
//! A hex editor with vim like keybindings written in Rust.

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

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

mod draw;
use draw::draw;
use draw::get_absolute_draw_indices;
mod find;
use find::FindOptSubset;

extern crate ncurses;
use ncurses::*;

extern crate getopts;
use getopts::Options;

extern crate pest;
#[macro_use]
extern crate pest_derive;

use pest::Parser;

#[derive(Parser)]
#[grammar = "cmd.pest"]
struct IdentParser;

extern crate memmem;
use memmem::{Searcher, TwoWaySearcher};

#[derive(PartialEq, Copy, Clone)]
pub enum CursorSelects {
    LeftNibble,
    RightNibble,
    AsciiChar,
}

#[derive(Copy, Clone)]
pub struct CursorState {
    pos: usize,
    sel: CursorSelects,
}

fn main() {
    const VERSION: &str = env!("CARGO_PKG_VERSION");
    let mut buf = vec![];
    let mut cursor = CursorState {
        pos: 0,
        sel: CursorSelects::LeftNibble,
    };
    // 0 = display data from first line of file
    let mut screenoffset: usize = 0;
    const COLS: usize = 16;
    let mut command = String::new();
    let mut lastcommand = String::new();
    let mut autoparse = String::new();
    let mut debug = String::new();

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

    let args: Vec<_> = env::args().collect();
    let program = args[0].clone();
    let mut opts = Options::new();
    opts.optflag("h", "help", "print this help menu");
    opts.optflag("v", "version", "print the version");
    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => {
            endwin();
            println!("{}", f.to_string());
            println!("Usage: {} FILE [options]", program);
            return;
        }
    };
    if matches.opt_present("v") {
        endwin();
        println!("Version: {}", VERSION);
        return;
    }
    if matches.opt_present("h") {
        endwin();
        println!("Usage: {} FILE [options]", program);
        return;
    }

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

    let patharg = match matches.free.is_empty() {
        true => String::new(),
        false => matches.free[0].clone(),
    };
    let path = Path::new(&patharg);

    if patharg.is_empty() {
        endwin();
        println!("Patharg is empty!\n");
        return;
    }

    let mut file = match OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(&path)
    {
        Err(why) => {
            endwin();
            println!("Could not open {}: {}", path.display(), why.to_string());
            return;
        }
        Ok(file) => file,
    };
    file.read_to_end(&mut buf)
        .ok()
        .expect("File could not be read.");

    let draw_range = get_absolute_draw_indices(buf.len(), COLS, screenoffset);
    draw(
        &buf[draw_range.0..draw_range.1],
        COLS,
        &command,
        &mut debug,
        cursor,
        screenoffset,
    );

    let mut quitnow = false;
    while quitnow == false {
        if autoparse.is_empty() {
            let key = std::char::from_u32(getch() as u32).unwrap();
            command.push_str(&key.clone().to_string());
        } else {
            command.push(autoparse.chars().nth(0).unwrap());
            autoparse.remove(0);
        }

        let parsethisstring = command.clone();
        let commands = IdentParser::parse(Rule::cmd_list, &parsethisstring)
            .unwrap_or_else(|e| panic!("{}", e));

        let mut clear = true;
        let mut save = false;
        for cmd in commands {
            match cmd.as_rule() {
                Rule::down => {
                    // debug.push_str(&format!("{:?}", cmd.as_rule()));
                    if cursor.pos + COLS < buf.len() {
                        // not at end
                        cursor.pos += COLS;
                    } else {
                        // when at end
                        if buf.len() != 0 {
                            // Suppress underflow
                            cursor.pos = buf.len() - 1;
                        }
                    }
                }
                Rule::up => {
                    if cursor.pos >= COLS {
                        cursor.pos -= COLS;
                    }
                }
                Rule::left => {
                    if cursor.sel == CursorSelects::AsciiChar {
                        if cursor.pos > 0 {
                            cursor.pos -= 1;
                        }
                    } else if cursor.sel == CursorSelects::RightNibble {
                        cursor.sel = CursorSelects::LeftNibble;
                    } else if cursor.sel == CursorSelects::LeftNibble {
                        if cursor.pos > 0 {
                            // not at start
                            cursor.sel = CursorSelects::RightNibble;
                            cursor.pos -= 1;
                        }
                    }
                }
                Rule::right => {
                    if cursor.sel == CursorSelects::AsciiChar {
                        if cursor.pos + 1 < buf.len() {
                            // not at end
                            cursor.pos += 1;
                        }
                    } else if cursor.sel == CursorSelects::LeftNibble {
                        cursor.sel = CursorSelects::RightNibble;
                    } else if cursor.sel == CursorSelects::RightNibble {
                        if cursor.pos + 1 < buf.len() {
                            // not at end
                            cursor.sel = CursorSelects::LeftNibble;
                            cursor.pos += 1;
                        }
                    }
                }
                Rule::start => {
                    cursor.pos -= cursor.pos % COLS; // jump to start of line
                    if cursor.sel == CursorSelects::RightNibble {
                        cursor.sel = CursorSelects::LeftNibble;
                    }
                }
                Rule::end => {
                    // check if no overflow
                    if cursor.pos - (cursor.pos % COLS) + (COLS - 1) < buf.len() {
                        // jump to end of line
                        cursor.pos = cursor.pos - (cursor.pos % COLS) + (COLS - 1);
                    } else {
                        // jump to end of line
                        cursor.pos = buf.len() - 1
                    }
                    if cursor.sel == CursorSelects::LeftNibble {
                        cursor.sel = CursorSelects::RightNibble;
                    }
                }
                Rule::bottom => {
                    cursor.pos = buf.len() - 1;
                    cursor.pos -= cursor.pos % COLS; // jump to start of line
                }
                Rule::replace => {
                    clear = false;
                }
                Rule::replacement => {
                    let key = command.chars().last().unwrap_or('x');
                    if cursor.sel == CursorSelects::AsciiChar {
                        if cursor.pos >= buf.len() {
                            buf.insert(cursor.pos, 0);
                        }
                        buf[cursor.pos] = key as u8;
                    } else {
                        let mask = if cursor.sel == CursorSelects::LeftNibble {
                            0x0F
                        } else {
                            0xF0
                        };
                        let shift = if cursor.sel == CursorSelects::LeftNibble {
                            4
                        } else {
                            0
                        };
                        if cursor.pos >= buf.len() {
                            buf.insert(cursor.pos, 0);
                        }
                        // Change the selected nibble
                        if let Some(c) = key.to_digit(16) {
                            buf[cursor.pos] = buf[cursor.pos] & mask | (c as u8) << shift;
                        }
                    }
                    lastcommand = command.clone();
                    clear = true;
                }
                Rule::replaceend => {
                    clear = true;
                }
                Rule::remove => {
                    // check if in valid range
                    if buf.len() > 0 && cursor.pos < buf.len() {
                        // remove the current char
                        buf.remove(cursor.pos);
                    }
                    // always perform the movement if possible
                    if cursor.pos > 0 && cursor.pos >= buf.len() {
                        cursor.pos -= 1;
                    }
                    lastcommand = command.clone();
                }
                Rule::dd => {
                    let amount: usize = cmd.as_str().parse().unwrap_or(1);
                    // check if in valid range
                    if buf.len() > 0 && cursor.pos < buf.len() {
                        let startofline = cursor.pos - cursor.pos % COLS;
                        let mut endofline = cursor.pos - (cursor.pos % COLS) + (COLS * amount);
                        endofline = cmp::min(endofline, buf.len());
                        buf.drain(startofline..endofline);
                        if buf.len() <= 0 {
                            cursor.pos = 0; // Ensure cursor does not go below 0
                        }
                        if buf.len() > 0 && cursor.pos >= buf.len() {
                            cursor.pos = buf.len() - 1;
                        }
                    }
                    lastcommand = command.clone();
                    clear = true;
                }
                Rule::insert => {
                    // debug.push_str("next chars will be inserted!");
                    clear = false;
                }
                Rule::insertstuff => {
                    let key = command.chars().last().unwrap_or('x');

                    if cursor.sel == CursorSelects::LeftNibble {
                        // Left nibble
                        if let Some(c) = key.to_digit(16) {
                            buf.insert(cursor.pos, (c as u8) << 4);
                            cursor.sel = CursorSelects::RightNibble;
                        }
                    } else if cursor.sel == CursorSelects::RightNibble {
                        // Right nibble
                        if cursor.pos == buf.len() {
                            buf.insert(cursor.pos, 0);
                        }
                        if let Some(c) = key.to_digit(16) {
                            buf[cursor.pos] = buf[cursor.pos] & 0xF0 | c as u8;
                            cursor.sel = CursorSelects::LeftNibble;
                            cursor.pos += 1;
                        }
                    } else if cursor.sel == CursorSelects::AsciiChar {
                        buf.insert(cursor.pos, key as u8);
                        cursor.pos += 1;
                    }

                    clear = false;
                }
                Rule::insertend => {
                    lastcommand = command.clone();
                    clear = true;
                }
                Rule::jumpascii => {
                    if cursor.sel == CursorSelects::AsciiChar {
                        cursor.sel = CursorSelects::LeftNibble;
                    } else {
                        cursor.sel = CursorSelects::AsciiChar;
                    }
                }
                Rule::helpfile => {
                    command.pop();
                    command.push_str("No helpfile yet");
                    clear = false;
                }
                Rule::repeat => {
                    autoparse = lastcommand.clone();
                }
                Rule::gg => {
                    let linenr: usize = cmd.as_str().parse().unwrap_or(0);
                    cursor.pos = linenr * COLS; // jump to the line
                    if cursor.pos > buf.len() {
                        // detect file end
                        cursor.pos = buf.len();
                    }
                    cursor.pos -= cursor.pos % COLS; // jump to start of line
                    clear = true;
                }
                Rule::backspace => {
                    command.pop();
                    command.pop();
                    clear = false;
                }
                Rule::saveandexit => {
                    save = true;
                    quitnow = true;
                }
                Rule::exit => quitnow = true,
                Rule::save => save = true,
                Rule::escape => (),
                _ => (),
            }

            for inner_cmd in cmd.into_inner() {
                match inner_cmd.as_rule() {
                    Rule::searchstr => {
                        let search = inner_cmd.as_str().as_bytes();
                        let foundpos = TwoWaySearcher::new(&search);
                        cursor.pos = foundpos.search_in(&buf).unwrap_or(cursor.pos);
                    }
                    Rule::searchbytes => {
                        let search = inner_cmd.as_str().as_bytes();
                        let mut needle = vec![];
                        for i in 0..search.len() {
                            let nibble = match search[i] as u8 {
                                c @ 48..=57 => c - 48, // Numbers from 0 to 9
                                b'x' => 0x10,          // x is the wildcard
                                b'X' => 0x10,          // X is the wildcard
                                c @ b'a'..=b'f' => c - 87,
                                c @ b'A'..=b'F' => c - 55,
                                _ => panic!("Should not get to this position!"),
                            };
                            needle.push(nibble);
                        }
                        cursor.pos = buf.find_subset(&needle).unwrap_or(cursor.pos);
                        // debug.push_str(&format!("Searching for: {:?}", needle ));
                    }
                    Rule::gatherone => clear = false,
                    _ => {
                        command.push_str(&format!("no rule for {:?} ", inner_cmd.as_rule()));
                        clear = false;
                    }
                };
            }
            if save {
                if path.exists() {
                    let mut file = match OpenOptions::new()
                        .read(true)
                        .write(true)
                        .create(true)
                        .open(&path)
                    {
                        Err(why) => {
                            panic!("Could not open {}: {}", path.display(), why.to_string())
                        }
                        Ok(file) => file,
                    };
                    file.seek(SeekFrom::Start(0))
                        .ok()
                        .expect("Filepointer could not be set to 0");
                    file.write_all(&mut buf)
                        .ok()
                        .expect("File could not be written.");
                    file.set_len(buf.len() as u64)
                        .ok()
                        .expect("File could not be set to correct lenght.");
                    command.push_str("File saved!");
                } else {
                    command.push_str("Careful, file could not be saved!");
                }
                // TODO: define filename during runtime
                save = false;
            }
            if clear {
                command.clear();
            }

            // Always move screen when cursor leaves screen
            if cursor.pos > (screenheight + screenoffset - 1) * COLS - 1 {
                screenoffset = 2 + cursor.pos / COLS - screenheight;
            }
            if cursor.pos < screenoffset * COLS {
                screenoffset = cursor.pos / COLS;
            }
        }

        let draw_range = get_absolute_draw_indices(buf.len(), COLS, screenoffset);
        draw(
            &buf[draw_range.0..draw_range.1],
            COLS,
            &command,
            &mut debug,
            cursor,
            screenoffset,
        );
    }

    refresh();
    endwin();
}
<|endoftext|>


<commit_before>extern crate hkg;
extern crate rustbox;
extern crate rustc_serialize;
extern crate chrono;
extern crate kuchiki;

use kuchiki::traits::*;
use kuchiki::NodeRef;

use std::default::Default;

use rustbox::{Color, RustBox, Key};
use rustc_serialize::json;
use rustc_serialize::json::Json;

use chrono::*;

use hkg::utility::cache;
use hkg::model::ListTopicItem;
use hkg::model::ShowItem;
use hkg::model::ShowReplyItem;
use hkg::model::UrlQueryItem;

use std::path::Path;

use std::io::prelude::*;
use std::fs::File;
use std::io::{Error, ErrorKind};

use std::io::Cursor;
use std::io::BufReader;

#[derive(PartialEq, Eq, Copy, Clone)]
enum Status {
    List,
    Show,
}

fn main() {

    // GUI init
    let rustbox = match RustBox::init(Default::default()) {
        Result::Ok(v) => v,
        Result::Err(e) => panic!("{}", e),
    };

    rustbox.print(1,
                  1,
                  rustbox::RB_NORMAL,
                  Color::White,
                  Color::Black,
                  &format!("start => {}", Local::now()));

    let title = String::from("高登");
    let s = cache::readfile(String::from("data/topics.json"));
    let collection: Vec<ListTopicItem> = json::decode(&s).unwrap();

    // initialize show with empty page
    let mut show_file;
    let mut show_item = ShowItem {
        url_query: UrlQueryItem { message: String::from("") },
        replies: vec![],
        page: 0,
        max_page: 0,
        reply_count: String::from(""),
        title: String::from(""),
    };

    let mut status = String::from("> ");

    let mut state = Status::List;
    let mut prev_state = state;
    let mut prev_width = rustbox.width();

    let mut list = hkg::screen::list::List::new(&rustbox);
    let mut show = hkg::screen::show::Show::new(&rustbox);

    let mut builder = hkg::builder::Builder::new();
    loop {

        // show UI
        if prev_state != state {
            hkg::screen::common::clear(&rustbox); // clear screen when switching state
            prev_state = state;
        }

        let url = &collection[1].title.url;

        rustbox.print(1, 2, rustbox::RB_NORMAL, Color::White, Color::Black, url);

        rustbox.print(1,
                      3,
                      rustbox::RB_NORMAL,
                      Color::White,
                      Color::Black,
                      &format!("before parse => {}", Local::now()));

        let postid = "6360604";
        let page = 1;
        let path1 = format!("data/html/{postid}/show_{page}.html",
                            postid = postid,
                            page = page);

        let document = kuchiki::parse_html().from_utf8().from_file(&path1).unwrap();

        let si = builder.show_item(&document, &url);

        rustbox.print(1,
                      5,
                      rustbox::RB_NORMAL,
                      Color::White,
                      Color::Black,
                      &format!("url_query->message: {} title:{} reploy count: {} page: {} max_page: {}",
                               si.url_query.message,
                               si.title,
                               si.reply_count,
                               si.page,
                               si.max_page));

       for (index, item) in si.replies.iter().enumerate() {
           rustbox.print(1,
                         index + 7,
                         rustbox::RB_NORMAL,
                         Color::White,
                         Color::Black,
                         &format!("{:<2}={:?}", index, item));
       }

        // let mut f = File::create("foo.txt").unwrap();
        // // let uu :Vec<u8> = ss.chars;
        // f.write_all(ss.as_bytes());

        rustbox.print(1,
                      4,
                      rustbox::RB_NORMAL,
                      Color::White,
                      Color::Black,
                      &format!("after parse => {}", Local::now()));

        // match state {
        //     Status::List => {
        //         list.print(&title, &collection);
        //     }
        //     Status::Show => {
        //         show.print(&title, &show_item);
        //     }
        // }

        print_status(&rustbox, &status);

        rustbox.present();

        match rustbox.poll_event(false) {
            Ok(rustbox::Event::KeyEvent(key)) => {

                if prev_width != rustbox.width() {
                    hkg::screen::common::clear(&rustbox);
                    prev_width = rustbox.width();
                }

                match key {
                    Key::Char('q') => {
                        break;
                    }
                    Key::PageUp => {
                        let w = rustbox.width();
                        status = format_status(status, w, " PU");

                        match state {
                            Status::List => {}
                            Status::Show => {
                                let bh = show.body_height();
                                if show.scrollUp(bh) {
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }
                    }
                    Key::PageDown => {
                        let w = rustbox.width();
                        status = format_status(status, w, " PD");

                        match state {
                            Status::List => {}
                            Status::Show => {
                                let bh = show.body_height();
                                if show.scrollDown(bh) {
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }
                    }
                    Key::Up => {
                        let w = rustbox.width();
                        status = format_status(status, w, "U");

                        match state {
                            Status::List => {
                                let tmp = list.get_selected_topic();
                                if tmp > 1 {
                                    list.select_topic(tmp - 1);
                                }
                            }
                            Status::Show => {
                                if show.scrollUp(2) {
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }

                    }
                    Key::Down => {
                        let w = rustbox.width();
                        status = format_status(status, w, "D");

                        match state {
                            Status::List => {
                                let tmp = list.get_selected_topic();
                                if tmp < list.body_height() {
                                    list.select_topic(tmp + 1);
                                }
                            }
                            Status::Show => {
                                if show.scrollDown(2) {
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }
                    }
                    Key::Left => {
                        match state {
                            Status::List => {}
                            Status::Show => {
                                if show_item.page > 1 {
                                    let show_file_path = format!("data/{postid}/show_{page}.json",
                                                                 postid = show_item.url_query
                                                                                .message,
                                                                 page = show_item.page - 1);

                                    show_file = cache::readfile(String::from(show_file_path));
                                    show_item = json::decode(&show_file).unwrap();
                                    show.resetY();
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }
                    }
                    Key::Right => {
                        match state {
                            Status::List => {}
                            Status::Show => {
                                if show_item.max_page > show_item.page {
                                    let show_file_path = format!("data/{postid}/show_{page}.json",
                                                                 postid = show_item.url_query
                                                                                .message,
                                                                 page = show_item.page + 1);

                                    show_file = cache::readfile(String::from(show_file_path));
                                    show_item = json::decode(&show_file).unwrap();
                                    show.resetY();
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }
                    }
                    Key::Enter => {
                        let w = rustbox.width();
                        status = format_status(status, w, "E");
                        match state {
                            Status::List => {

                                let index = list.get_selected_topic();
                                if index > 0 {
                                    let topic_item = &collection[index - 1];

                                    let postid = &topic_item.title.url_query.message;

                                    let show_file_path = format!("data/{postid}/show_{page}.json",
                                                                 postid = postid,
                                                                 page = 1);

                                    if Path::new(&show_file_path).exists() {
                                        show_file = cache::readfile(String::from(show_file_path));
                                        show_item = json::decode(&show_file).unwrap();
                                        show.resetY();
                                        hkg::screen::common::clear(&rustbox);
                                        state = Status::Show;
                                    } else {
                                        let w = rustbox.width();
                                        status = format_status(status,
                                                               w,
                                                               &format!(" postid {} not found.",
                                                                        postid));
                                    }
                                }
                            }
                            Status::Show => {}
                        }
                    }
                    Key::Backspace => {
                        let w = rustbox.width();
                        status = format_status(status, w, "B");
                        match state {
                            Status::List => {}
                            Status::Show => {
                                state = Status::List;
                            }
                        }
                    }

                    _ => {}
                }
            }
            Err(e) => panic!("{}", e),
            _ => {}
        }
    }
}

fn print_status(rustbox: &rustbox::RustBox, status: &str) {
    // for status bar only
    let w = rustbox.width();
    let h = rustbox.height();

    let status_width = if w > status.len() {
        w - status.len()
    } else {
        0
    };
    let status_spacing = (0..status_width).map(|_| " ").collect::<Vec<_>>().join("");

    rustbox.print(0,
                  h - 1,
                  rustbox::RB_BOLD,
                  Color::White,
                  Color::Black,
                  &format!("{status}{status_spacing}",
                           status = status,
                           status_spacing = status_spacing));

}

fn format_status(status: String, w: usize, s: &str) -> String {
    if status.len() >= w {
        String::from(format!("{}{}", &"> ", s))
    } else {
        String::from(format!("{}{}", &status, s))
    }
}

// for (jndex, elm) in tr.as_node().select(".repliers_right .ContentGrid").unwrap().enumerate() {
//     let content = elm.as_node().text_contents();
//     let name = &elm.name;
//     rustbox.print(1, jndex + index + 4, rustbox::RB_NORMAL, Color::White, Color::Black, &format!("[{:<2}][{:<2}]={:?}", index, jndex, content));
// }

// let content = tr.text_contents();
// rustbox.print(1,
//               index + 4,
//               rustbox::RB_NORMAL,
//               Color::White,
//               Color::Black,
//               &format!("[{:<2}]={:?}", index, content));

// for (jndex, div) in tr.as_node().children().enumerate() {
//     let content = div.as_text();
//     rustbox.print(1, jndex + index + 4, rustbox::RB_NORMAL, Color::White, Color::Black, &format!("[{:<2}]={:?}", index + jndex, content));
// }

// for (jndex, div) in tr.as_node().select(".repliers_right").unwrap().enumerate() {
//     let content = div.as_node().as_text();
//     rustbox.print(1, jndex + index + 4, rustbox::RB_NORMAL, Color::White, Color::Black, &format!("[{:<2}]={:?}", index + jndex, content));
// }

// for (jndex, div) in tr.as_node().select(".repliers_right").unwrap().collect::<Vec<_>>().iter().enumerate() {
//     let content = div.as_node().as_text();
//     rustbox.print(1, jndex + index + 4, rustbox::RB_NORMAL, Color::White, Color::Black, &format!("[{:<2}]={:?}", index + jndex, content));
// }

// let c = &tr.as_node().select(".repliers_right .ContentGrid").unwrap().collect::<Vec<_>>()[0];
// let content = c.as_node().as_text();
// rustbox.print(1, index + 4, rustbox::RB_NORMAL, Color::White, Color::Black, &format!("[{:<2}]={:?}", index, content));

// fn date_operation_example(rustbox: &rustbox::RustBox) {
//     let now = Local::now();
//
//     let dt1 = match Local.datetime_from_str("30/4/2016 9:22", "%d/%m/%Y %H:%M") {
//         Ok(v) => v,
//         Err(e) => Local::now(),
//     };
//
//     let dt2 = now.checked_sub(Duration::seconds(46)).unwrap();
//     let dt3 = now.checked_sub(Duration::minutes(6)).unwrap();
//     let dt4 = now.checked_sub(Duration::days(17)).unwrap();
//     let dt5 = now.checked_sub(Duration::weeks(9)).unwrap();
//
//     rustbox.print(0,
//                   0,
//                   rustbox::RB_BOLD,
//                   Color::White,
//                   Color::Black,
//                   &format!("{} {} {} {}",
//                    duration_format(&(now - dt2)),
//                    duration_format(&(now - dt3)),
//                    duration_format(&(now - dt4)),
//                    duration_format(&(now - dt5))
//               ));
//
// }

// fn debug_load_and_print_topics() {
//     let s = cache::readfile(String::from("topics.json"));
//     let collection: Vec<TopicItem> = json::decode(&s).unwrap();
//
//     println!("topics {:?}", collection.len());
//     debug_print_topics(collection);
// }
//
// fn debug_print_topics(collection: Vec<TopicItem>) {
//     for (i, item) in collection.iter().enumerate() {
//
//         println!("item[{}]= {title} {author_name} {last_replied_date} {last_replied_time} \
//                   {reply_count} {rating}",
//                  i,
//                  title = item.titles[0].text,
//                  author_name = item.author.name,
//                  last_replied_date = item.last_replied_date,
//                  last_replied_time = item.last_replied_time,
//                  reply_count = item.reply_count,
//                  rating = item.rating);
//     }
// }
<commit_msg>extract show_item_build_example<commit_after>extern crate hkg;
extern crate rustbox;
extern crate rustc_serialize;
extern crate chrono;
extern crate kuchiki;

use kuchiki::traits::*;
use kuchiki::NodeRef;

use std::default::Default;

use rustbox::{Color, RustBox, Key};
use rustc_serialize::json;
use rustc_serialize::json::Json;

use chrono::*;

use hkg::utility::cache;
use hkg::model::ListTopicItem;
use hkg::model::ShowItem;
use hkg::model::ShowReplyItem;
use hkg::model::UrlQueryItem;

use std::path::Path;

use std::io::prelude::*;
use std::fs::File;
use std::io::{Error, ErrorKind};

use std::io::Cursor;
use std::io::BufReader;

#[derive(PartialEq, Eq, Copy, Clone)]
enum Status {
    List,
    Show,
}

fn main() {

    // GUI init
    let rustbox = match RustBox::init(Default::default()) {
        Result::Ok(v) => v,
        Result::Err(e) => panic!("{}", e),
    };

    rustbox.print(1,
                  1,
                  rustbox::RB_NORMAL,
                  Color::White,
                  Color::Black,
                  &format!("start => {}", Local::now()));

    let title = String::from("高登");
    let s = cache::readfile(String::from("data/topics.json"));
    let collection: Vec<ListTopicItem> = json::decode(&s).unwrap();

    // initialize show with empty page
    let mut show_file;
    let mut show_item = ShowItem {
        url_query: UrlQueryItem { message: String::from("") },
        replies: vec![],
        page: 0,
        max_page: 0,
        reply_count: String::from(""),
        title: String::from(""),
    };

    let mut status = String::from("> ");

    let mut state = Status::List;
    let mut prev_state = state;
    let mut prev_width = rustbox.width();

    let mut list = hkg::screen::list::List::new(&rustbox);
    let mut show = hkg::screen::show::Show::new(&rustbox);

    loop {

        // show UI
        if prev_state != state {
            hkg::screen::common::clear(&rustbox); // clear screen when switching state
            prev_state = state;
        }

        show_item_build_example(&rustbox, &collection);

        // let mut f = File::create("foo.txt").unwrap();
        // // let uu :Vec<u8> = ss.chars;
        // f.write_all(ss.as_bytes());

        rustbox.print(1,
                      4,
                      rustbox::RB_NORMAL,
                      Color::White,
                      Color::Black,
                      &format!("after parse => {}", Local::now()));

        // match state {
        //     Status::List => {
        //         list.print(&title, &collection);
        //     }
        //     Status::Show => {
        //         show.print(&title, &show_item);
        //     }
        // }

        print_status(&rustbox, &status);

        rustbox.present();

        match rustbox.poll_event(false) {
            Ok(rustbox::Event::KeyEvent(key)) => {

                if prev_width != rustbox.width() {
                    hkg::screen::common::clear(&rustbox);
                    prev_width = rustbox.width();
                }

                match key {
                    Key::Char('q') => {
                        break;
                    }
                    Key::PageUp => {
                        let w = rustbox.width();
                        status = format_status(status, w, " PU");

                        match state {
                            Status::List => {}
                            Status::Show => {
                                let bh = show.body_height();
                                if show.scrollUp(bh) {
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }
                    }
                    Key::PageDown => {
                        let w = rustbox.width();
                        status = format_status(status, w, " PD");

                        match state {
                            Status::List => {}
                            Status::Show => {
                                let bh = show.body_height();
                                if show.scrollDown(bh) {
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }
                    }
                    Key::Up => {
                        let w = rustbox.width();
                        status = format_status(status, w, "U");

                        match state {
                            Status::List => {
                                let tmp = list.get_selected_topic();
                                if tmp > 1 {
                                    list.select_topic(tmp - 1);
                                }
                            }
                            Status::Show => {
                                if show.scrollUp(2) {
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }

                    }
                    Key::Down => {
                        let w = rustbox.width();
                        status = format_status(status, w, "D");

                        match state {
                            Status::List => {
                                let tmp = list.get_selected_topic();
                                if tmp < list.body_height() {
                                    list.select_topic(tmp + 1);
                                }
                            }
                            Status::Show => {
                                if show.scrollDown(2) {
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }
                    }
                    Key::Left => {
                        match state {
                            Status::List => {}
                            Status::Show => {
                                if show_item.page > 1 {
                                    let show_file_path = format!("data/{postid}/show_{page}.json",
                                                                 postid = show_item.url_query
                                                                                .message,
                                                                 page = show_item.page - 1);

                                    show_file = cache::readfile(String::from(show_file_path));
                                    show_item = json::decode(&show_file).unwrap();
                                    show.resetY();
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }
                    }
                    Key::Right => {
                        match state {
                            Status::List => {}
                            Status::Show => {
                                if show_item.max_page > show_item.page {
                                    let show_file_path = format!("data/{postid}/show_{page}.json",
                                                                 postid = show_item.url_query
                                                                                .message,
                                                                 page = show_item.page + 1);

                                    show_file = cache::readfile(String::from(show_file_path));
                                    show_item = json::decode(&show_file).unwrap();
                                    show.resetY();
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }
                    }
                    Key::Enter => {
                        let w = rustbox.width();
                        status = format_status(status, w, "E");
                        match state {
                            Status::List => {

                                let index = list.get_selected_topic();
                                if index > 0 {
                                    let topic_item = &collection[index - 1];

                                    let postid = &topic_item.title.url_query.message;

                                    let show_file_path = format!("data/{postid}/show_{page}.json",
                                                                 postid = postid,
                                                                 page = 1);

                                    if Path::new(&show_file_path).exists() {
                                        show_file = cache::readfile(String::from(show_file_path));
                                        show_item = json::decode(&show_file).unwrap();
                                        show.resetY();
                                        hkg::screen::common::clear(&rustbox);
                                        state = Status::Show;
                                    } else {
                                        let w = rustbox.width();
                                        status = format_status(status,
                                                               w,
                                                               &format!(" postid {} not found.",
                                                                        postid));
                                    }
                                }
                            }
                            Status::Show => {}
                        }
                    }
                    Key::Backspace => {
                        let w = rustbox.width();
                        status = format_status(status, w, "B");
                        match state {
                            Status::List => {}
                            Status::Show => {
                                state = Status::List;
                            }
                        }
                    }

                    _ => {}
                }
            }
            Err(e) => panic!("{}", e),
            _ => {}
        }
    }
}

fn print_status(rustbox: &rustbox::RustBox, status: &str) {
    // for status bar only
    let w = rustbox.width();
    let h = rustbox.height();

    let status_width = if w > status.len() {
        w - status.len()
    } else {
        0
    };
    let status_spacing = (0..status_width).map(|_| " ").collect::<Vec<_>>().join("");

    rustbox.print(0,
                  h - 1,
                  rustbox::RB_BOLD,
                  Color::White,
                  Color::Black,
                  &format!("{status}{status_spacing}",
                           status = status,
                           status_spacing = status_spacing));

}

fn format_status(status: String, w: usize, s: &str) -> String {
    if status.len() >= w {
        String::from(format!("{}{}", &"> ", s))
    } else {
        String::from(format!("{}{}", &status, s))
    }
}

fn show_item_build_example(rustbox: &rustbox::RustBox, collection: &Vec<ListTopicItem>) {

    let mut builder = hkg::builder::Builder::new();

    let url = &collection[1].title.url;

    rustbox.print(1, 2, rustbox::RB_NORMAL, Color::White, Color::Black, url);

    rustbox.print(1,
                  3,
                  rustbox::RB_NORMAL,
                  Color::White,
                  Color::Black,
                  &format!("before parse => {}", Local::now()));

    let postid = "6360604";
    let page = 1;
    let path1 = format!("data/html/{postid}/show_{page}.html",
                        postid = postid,
                        page = page);

    let document = kuchiki::parse_html().from_utf8().from_file(&path1).unwrap();

    let si = builder.show_item(&document, &url);

    rustbox.print(1,
                  5,
                  rustbox::RB_NORMAL,
                  Color::White,
                  Color::Black,
                  &format!("url_query->message: {} title:{} reploy count: {} page: {} max_page: \
                            {}",
                           si.url_query.message,
                           si.title,
                           si.reply_count,
                           si.page,
                           si.max_page));

    for (index, item) in si.replies.iter().enumerate() {
        rustbox.print(1,
                      index + 7,
                      rustbox::RB_NORMAL,
                      Color::White,
                      Color::Black,
                      &format!("{:<2}={:?}", index, item));
    }

}

// for (jndex, elm) in tr.as_node().select(".repliers_right .ContentGrid").unwrap().enumerate() {
//     let content = elm.as_node().text_contents();
//     let name = &elm.name;
//     rustbox.print(1, jndex + index + 4, rustbox::RB_NORMAL, Color::White, Color::Black, &format!("[{:<2}][{:<2}]={:?}", index, jndex, content));
// }

// let content = tr.text_contents();
// rustbox.print(1,
//               index + 4,
//               rustbox::RB_NORMAL,
//               Color::White,
//               Color::Black,
//               &format!("[{:<2}]={:?}", index, content));

// for (jndex, div) in tr.as_node().children().enumerate() {
//     let content = div.as_text();
//     rustbox.print(1, jndex + index + 4, rustbox::RB_NORMAL, Color::White, Color::Black, &format!("[{:<2}]={:?}", index + jndex, content));
// }

// for (jndex, div) in tr.as_node().select(".repliers_right").unwrap().enumerate() {
//     let content = div.as_node().as_text();
//     rustbox.print(1, jndex + index + 4, rustbox::RB_NORMAL, Color::White, Color::Black, &format!("[{:<2}]={:?}", index + jndex, content));
// }

// for (jndex, div) in tr.as_node().select(".repliers_right").unwrap().collect::<Vec<_>>().iter().enumerate() {
//     let content = div.as_node().as_text();
//     rustbox.print(1, jndex + index + 4, rustbox::RB_NORMAL, Color::White, Color::Black, &format!("[{:<2}]={:?}", index + jndex, content));
// }

// let c = &tr.as_node().select(".repliers_right .ContentGrid").unwrap().collect::<Vec<_>>()[0];
// let content = c.as_node().as_text();
// rustbox.print(1, index + 4, rustbox::RB_NORMAL, Color::White, Color::Black, &format!("[{:<2}]={:?}", index, content));

// fn date_operation_example(rustbox: &rustbox::RustBox) {
//     let now = Local::now();
//
//     let dt1 = match Local.datetime_from_str("30/4/2016 9:22", "%d/%m/%Y %H:%M") {
//         Ok(v) => v,
//         Err(e) => Local::now(),
//     };
//
//     let dt2 = now.checked_sub(Duration::seconds(46)).unwrap();
//     let dt3 = now.checked_sub(Duration::minutes(6)).unwrap();
//     let dt4 = now.checked_sub(Duration::days(17)).unwrap();
//     let dt5 = now.checked_sub(Duration::weeks(9)).unwrap();
//
//     rustbox.print(0,
//                   0,
//                   rustbox::RB_BOLD,
//                   Color::White,
//                   Color::Black,
//                   &format!("{} {} {} {}",
//                    duration_format(&(now - dt2)),
//                    duration_format(&(now - dt3)),
//                    duration_format(&(now - dt4)),
//                    duration_format(&(now - dt5))
//               ));
//
// }

// fn debug_load_and_print_topics() {
//     let s = cache::readfile(String::from("topics.json"));
//     let collection: Vec<TopicItem> = json::decode(&s).unwrap();
//
//     println!("topics {:?}", collection.len());
//     debug_print_topics(collection);
// }
//
// fn debug_print_topics(collection: Vec<TopicItem>) {
//     for (i, item) in collection.iter().enumerate() {
//
//         println!("item[{}]= {title} {author_name} {last_replied_date} {last_replied_time} \
//                   {reply_count} {rating}",
//                  i,
//                  title = item.titles[0].text,
//                  author_name = item.author.name,
//                  last_replied_date = item.last_replied_date,
//                  last_replied_time = item.last_replied_time,
//                  reply_count = item.reply_count,
//                  rating = item.rating);
//     }
// }
<|endoftext|>


<commit_before>use std::cell::Cell;

struct Coordinates {
    x: Cell<i32>,
    y: Cell<i32>,
}

fn main() {
    let origin = Coordinates { x: Cell::new(0), y: Cell::new(0) };

    // borrow the value so that it can change (using the '&' symbol borrows the var)
    move_up(&origin.y);
    move_down(&origin.y);
    move_right(&origin.x);
    move_left(&origin.x);

    println!("X is {:?} and Y is {:?}", origin.x, origin.y);
}

fn move_up(y_cord: &Cell<i32>) -> &Cell<i32> {
    let new_val = y_cord.get(); // get the value and store it
    y_cord.set(new_val + 1); // modify the stored value and set the incoming param
    y_cord // no semicolon here because it is the return value
}

fn move_down(y_cord: &Cell<i32>) -> &Cell<i32> {
    let new_val = y_cord.get(); // get the value and store it
    y_cord.set(new_val - 1); // modify the stored value and set the incoming param
    y_cord // no semicolon here because it is the return value
}

fn move_right(x_cord: &Cell<i32>) -> &Cell<i32> {
    let new_val = x_cord.get(); // get the value and store it
    x_cord.set(new_val + 1); // modify the stored value and set the incoming param
    x_cord // no semicolon here because it is the return value
}

fn move_left(x_cord: &Cell<i32>) -> &Cell<i32> {
    let new_val = x_cord.get(); // get the value and store it
    x_cord.set(new_val - 1); // modify the stored value and set the incoming param
    x_cord // no semicolon here because it is the return value
}

#[test]
fn it_can_move() {
    let origin = Coordinates { x: Cell::new(0), y: Cell::new(0) };

    // test it starts at 0

    assert_eq!(0, origin.y.get());

    // test it can move up one

    move_up(&origin.y);

    assert_eq!(1, origin.y.get());

    // test it can continue to move up and theat move logic is valid

    move_up(&origin.y);
    move_up(&origin.y);
    move_up(&origin.y);

    assert_eq!(4, origin.y.get());

    for _ in 0..19 { // _ instead of x so that rust does not complain about x not being used
        move_up(&origin.y); // moves up 19 times
    }

    assert_eq!(23, origin.y.get());

    // test it can move down

    for _ in 0..19 {
        move_down(&origin.y);
    }

    assert_eq!(4, origin.y.get());

    // test it can move right

    for _ in 0..19 {
        move_right(&origin.x);
    }

    assert_eq!(19, origin.x.get());

    // test it can move left

    for _ in 0..10 {
        move_left(&origin.x);
    }

    assert_eq!(9, origin.x.get());
}
<commit_msg>working state<commit_after>use std::cell::Cell;

struct Coordinates {
    x: Cell<i32>,
    y: Cell<i32>,
}

fn main() {
    let bike = Coordinates { x: Cell::new(0), y: Cell::new(0) };
    let mut previous_positions = vec![];
    // borrow the value so that it can change (using the '&' symbol borrows the var)
    move_up(&bike.y);
    previous_positions.push(&bike);
    move_down(&bike.y);
    previous_positions.push(&bike);
    move_right(&bike.x);
    previous_positions.push(&bike);
    move_left(&bike.x);
    previous_positions.push(&bike);

    println!("X is {:?}, Y is {:?}", bike.x, bike.y);
}

fn move_up(y_cord: &Cell<i32>) -> &Cell<i32> {
    let new_val = y_cord.get(); // get the value and store it
    y_cord.set(new_val + 1); // modify the stored value and set the incoming param
    y_cord // no semicolon here because it is the return value
}

fn move_down(y_cord: &Cell<i32>) -> &Cell<i32> {
    let new_val = y_cord.get(); // get the value and store it
    y_cord.set(new_val - 1); // modify the stored value and set the incoming param
    y_cord // no semicolon here because it is the return value
}

fn move_right(x_cord: &Cell<i32>) -> &Cell<i32> {
    let new_val = x_cord.get(); // get the value and store it
    x_cord.set(new_val + 1); // modify the stored value and set the incoming param
    x_cord // no semicolon here because it is the return value
}

fn move_left(x_cord: &Cell<i32>) -> &Cell<i32> {
    let new_val = x_cord.get(); // get the value and store it
    x_cord.set(new_val - 1); // modify the stored value and set the incoming param
    x_cord // no semicolon here because it is the return value
}

#[test]
fn it_can_move() {
    let bike = Coordinates { x: Cell::new(0), y: Cell::new(0) };

    // test it starts at 0

    assert_eq!(0, bike.y.get());

    // test it can move up one

    move_up(&bike.y);

    assert_eq!(1, bike.y.get());

    // test it can continue to move up and theat move logic is valid

    move_up(&bike.y);
    move_up(&bike.y);
    move_up(&bike.y);

    assert_eq!(4, bike.y.get());

    for _ in 0..19 { // _ instead of x so that rust does not complain about x not being used
        move_up(&bike.y); // moves up 19 times
    }

    assert_eq!(23, bike.y.get());

    // test it can move down

    for _ in 0..19 {
        move_down(&bike.y);
    }

    assert_eq!(4, bike.y.get());

    // test it can move right

    for _ in 0..19 {
        move_right(&bike.x);
    }

    assert_eq!(19, bike.x.get());

    // test it can move left

    for _ in 0..10 {
        move_left(&bike.x);
    }

    assert_eq!(9, bike.x.get());
}
<|endoftext|>


<commit_before>#![feature(test)]

extern crate human_name;
extern crate test;
extern crate rustc_serialize;

use std::env;
use std::process;
use std::io;
use std::io::BufReader;
use std::io::prelude::*;
use rustc_serialize::json::ToJson;

#[cfg_attr(rustfmt, rustfmt_skip)]
const USAGE: &'static str = "
Usage:
    human_name parse <name>
    human_name parse -
    human_name eq '<name1>' '<name2>'
    human_name eq '<name>' -

With the `eq` command, human_name will check names for equality, If '-' is the
first argument, it will expect newline-separated names from stdin to compare to
the second argument, and will print each which matches. Otherwise, it will compare
the two arguments, exiting with status 0 if the names are equal, and status 1 if
not.

With the `parse` command, it will run in parsing mode. If `-` is the argument,
it will expect newline-separated names to parse from stdin. Otherwise, it will
try to parse the arguments as a name, exiting with status 0 if it succeeds, and
status 1 otherwise. In either case it will print parsed output as JSON.
";

fn main() {
    let args: Vec<String> = env::args().collect();

    if args.len() == 4 && args[1] == "eq" {
        equality_mode(&args);
    } else if args.len() > 2 && args[1] == "parse" {
        parse_mode(&args);
    } else {
        writeln!(&mut std::io::stderr(), "{}", USAGE).ok().unwrap();
        process::exit(64);
    }
}

fn equality_mode(args: &Vec<String>) {
    if args[2] == "-" {
        let parsed_a = human_name::Name::parse(&args[3]);
        if parsed_a.is_none() {
            writeln!(&mut std::io::stderr(), "parse failed!").ok();
            process::exit(1);
        }

        let reader = BufReader::new(io::stdin());
        for line in reader.lines() {
            match line.ok() {
                Some(input) => {
                    let parsed_b = human_name::Name::parse(&input);
                    if parsed_a == parsed_b {
                        if !writeln!(&mut io::stdout(), "{}", input.trim()).is_ok() {
                            break;
                        }
                    };
                }
                None => {
                    break;
                }
            }
        }
    } else {
        let parsed_a = human_name::Name::parse(&args[2]);
        let parsed_b = human_name::Name::parse(&args[3]);
        if parsed_a.is_none() || parsed_b.is_none() {
            writeln!(&mut std::io::stdout(), "parse failed!").ok();
            process::exit(1);
        } else if parsed_a.unwrap() != parsed_b.unwrap() {
            writeln!(&mut std::io::stdout(), "not equal!").ok();
            process::exit(1);
        } else {
            writeln!(&mut std::io::stdout(), "equal").ok();
            process::exit(0);
        }
    }
}

fn parse_mode(args: &Vec<String>) {
    if args[2] == "-" {
        let reader = BufReader::new(io::stdin());
        for line in reader.lines() {
            match line.ok() {
                Some(input) => {
                    let parsed = human_name::Name::parse(&input);
                    let output = match parsed {
                        Some(name) => {
                            name.to_json().to_string()
                        }
                        None => {
                            "".to_string()
                        }
                    };

                    if !writeln!(&mut io::stdout(), "{}", output).is_ok() {
                        break;
                    }
                }
                None => {
                    break;
                }
            }
        }
    } else {
        let parsed = human_name::Name::parse(&args[2..].join(" "));
        if parsed.is_none() {
            process::exit(1);
        } else {
            println!("{}", parsed.unwrap().to_json().to_string());
        }
    }
}

#[cfg(test)]
mod bench {
    use std::collections::HashSet;
    use std::io::prelude::*;
    use std::io::BufReader;
    use std::fs::File;
    use test::Bencher;
    use test::black_box;
    use human_name::Name;

    #[bench]
    fn bench_parsing_first_last(b: &mut Bencher) {
        b.iter(|| {
            let parsed = Name::parse("Juan Garcia");
            black_box(parsed.is_none())
        })
    }

    #[bench]
    fn bench_parsing_sort_order(b: &mut Bencher) {
        b.iter(|| {
            let parsed = Name::parse("Garcia, J.Q.");
            black_box(parsed.is_none())
        })
    }

    #[bench]
    fn bench_parsing_needs_namecase(b: &mut Bencher) {
        b.iter(|| {
            let parsed = Name::parse("JAIME GARCIA");
            black_box(parsed.is_none())
        })
    }

    #[bench]
    fn bench_parsing_unparseable(b: &mut Bencher) {
        b.iter(|| {
            let parsed = Name::parse("foo@bar.com");
            black_box(parsed.is_none())
        })
    }

    #[bench]
    fn bench_parsing_complex(b: &mut Bencher) {
        b.iter(|| {
            let parsed = Name::parse("鈴木 Velasquez y Garcia, Dr. Juan Q. 'Don Juan' Xavier III");
            black_box(parsed.is_none())
        })
    }

    #[bench]
    fn bench_equality_equal(b: &mut Bencher) {
        let x = Name::parse("Jane Doe");
        let y = Name::parse("Jane H. Doe");

        b.iter(|| black_box(x == y))
    }

    #[bench]
    fn bench_equality_not_equal(b: &mut Bencher) {
        let x = Name::parse("Jane Doe");
        let y = Name::parse("Foo Bar");

        b.iter(|| black_box(x == y))
    }

    #[bench]
    fn bench_equality_close_to_equal(b: &mut Bencher) {
        let x = Name::parse("Jane Doe");
        let y = Name::parse("John Doe");

        b.iter(|| black_box(x == y))
    }

    #[bench]
    fn bench_parsing_many(b: &mut Bencher) {
        let f = File::open("tests/benchmark-names.txt").ok().unwrap();
        let reader = BufReader::new(f);
        let names: Vec<String> = reader.lines().map(|l| l.ok().unwrap()).collect();

        b.iter(move || {
            let mut valid = 0;
            let mut invalid = 0;

            for name in names.iter() {
                let parsed = Name::parse(&name);
                if parsed.is_none() {
                    invalid += 1;
                } else {
                    valid += 1;
                }
            }

            black_box(valid);
            black_box(invalid);
        })
    }

    #[bench]
    fn bench_equality_many(b: &mut Bencher) {
        let f = File::open("tests/benchmark-names.txt").ok().unwrap();
        let reader = BufReader::new(f);
        let names: Vec<Name> = reader.lines()
                                     .filter_map(|l| Name::parse(&l.ok().unwrap()))
                                     .collect();

        let mut deduped = HashSet::with_capacity(names.len() / 4);
        b.iter(|| {
            deduped.extend(names.iter());
            black_box(deduped.len())
        })
    }
}
<commit_msg>Less verbose command-line output in `eq` mode<commit_after>#![feature(test)]

extern crate human_name;
extern crate test;
extern crate rustc_serialize;

use std::env;
use std::process;
use std::io;
use std::io::BufReader;
use std::io::prelude::*;
use rustc_serialize::json::ToJson;

#[cfg_attr(rustfmt, rustfmt_skip)]
const USAGE: &'static str = "
Usage:
    human_name parse <name>
    human_name parse -
    human_name eq '<name1>' '<name2>'
    human_name eq '<name>' -

With the `eq` command, human_name will check names for equality, If '-' is the
first argument, it will expect newline-separated names from stdin to compare to
the second argument, and will print each which matches. Otherwise, it will compare
the two arguments, exiting with status 0 if the names are equal, and status 1 if
not.

With the `parse` command, it will run in parsing mode. If `-` is the argument,
it will expect newline-separated names to parse from stdin. Otherwise, it will
try to parse the arguments as a name, exiting with status 0 if it succeeds, and
status 1 otherwise. In either case it will print parsed output as JSON.
";

fn main() {
    let args: Vec<String> = env::args().collect();

    if args.len() == 4 && args[1] == "eq" {
        equality_mode(&args);
    } else if args.len() > 2 && args[1] == "parse" {
        parse_mode(&args);
    } else {
        writeln!(&mut std::io::stderr(), "{}", USAGE).ok().unwrap();
        process::exit(64);
    }
}

fn equality_mode(args: &Vec<String>) {
    if args[2] == "-" {
        let parsed_a = human_name::Name::parse(&args[3]);
        if parsed_a.is_none() {
            writeln!(&mut std::io::stderr(), "parse failed!").ok();
            process::exit(1);
        }

        let reader = BufReader::new(io::stdin());
        for line in reader.lines() {
            match line.ok() {
                Some(input) => {
                    let parsed_b = human_name::Name::parse(&input);
                    if parsed_a == parsed_b {
                        if !writeln!(&mut io::stdout(), "{}", input.trim()).is_ok() {
                            break;
                        }
                    };
                }
                None => {
                    break;
                }
            }
        }
    } else {
        let parsed_a = human_name::Name::parse(&args[2]);
        let parsed_b = human_name::Name::parse(&args[3]);
        if parsed_a.is_none() || parsed_b.is_none() {
            writeln!(&mut std::io::stdout(), "parse failed!").ok();
            process::exit(1);
        } else if parsed_a.unwrap() != parsed_b.unwrap() {
            writeln!(&mut std::io::stdout(), "n").ok();
            process::exit(1);
        } else {
            writeln!(&mut std::io::stdout(), "y").ok();
            process::exit(0);
        }
    }
}

fn parse_mode(args: &Vec<String>) {
    if args[2] == "-" {
        let reader = BufReader::new(io::stdin());
        for line in reader.lines() {
            match line.ok() {
                Some(input) => {
                    let parsed = human_name::Name::parse(&input);
                    let output = match parsed {
                        Some(name) => {
                            name.to_json().to_string()
                        }
                        None => {
                            "".to_string()
                        }
                    };

                    if !writeln!(&mut io::stdout(), "{}", output).is_ok() {
                        break;
                    }
                }
                None => {
                    break;
                }
            }
        }
    } else {
        let parsed = human_name::Name::parse(&args[2..].join(" "));
        if parsed.is_none() {
            process::exit(1);
        } else {
            println!("{}", parsed.unwrap().to_json().to_string());
        }
    }
}

#[cfg(test)]
mod bench {
    use std::collections::HashSet;
    use std::io::prelude::*;
    use std::io::BufReader;
    use std::fs::File;
    use test::Bencher;
    use test::black_box;
    use human_name::Name;

    #[bench]
    fn bench_parsing_first_last(b: &mut Bencher) {
        b.iter(|| {
            let parsed = Name::parse("Juan Garcia");
            black_box(parsed.is_none())
        })
    }

    #[bench]
    fn bench_parsing_sort_order(b: &mut Bencher) {
        b.iter(|| {
            let parsed = Name::parse("Garcia, J.Q.");
            black_box(parsed.is_none())
        })
    }

    #[bench]
    fn bench_parsing_needs_namecase(b: &mut Bencher) {
        b.iter(|| {
            let parsed = Name::parse("JAIME GARCIA");
            black_box(parsed.is_none())
        })
    }

    #[bench]
    fn bench_parsing_unparseable(b: &mut Bencher) {
        b.iter(|| {
            let parsed = Name::parse("foo@bar.com");
            black_box(parsed.is_none())
        })
    }

    #[bench]
    fn bench_parsing_complex(b: &mut Bencher) {
        b.iter(|| {
            let parsed = Name::parse("鈴木 Velasquez y Garcia, Dr. Juan Q. 'Don Juan' Xavier III");
            black_box(parsed.is_none())
        })
    }

    #[bench]
    fn bench_equality_equal(b: &mut Bencher) {
        let x = Name::parse("Jane Doe");
        let y = Name::parse("Jane H. Doe");

        b.iter(|| black_box(x == y))
    }

    #[bench]
    fn bench_equality_not_equal(b: &mut Bencher) {
        let x = Name::parse("Jane Doe");
        let y = Name::parse("Foo Bar");

        b.iter(|| black_box(x == y))
    }

    #[bench]
    fn bench_equality_close_to_equal(b: &mut Bencher) {
        let x = Name::parse("Jane Doe");
        let y = Name::parse("John Doe");

        b.iter(|| black_box(x == y))
    }

    #[bench]
    fn bench_parsing_many(b: &mut Bencher) {
        let f = File::open("tests/benchmark-names.txt").ok().unwrap();
        let reader = BufReader::new(f);
        let names: Vec<String> = reader.lines().map(|l| l.ok().unwrap()).collect();

        b.iter(move || {
            let mut valid = 0;
            let mut invalid = 0;

            for name in names.iter() {
                let parsed = Name::parse(&name);
                if parsed.is_none() {
                    invalid += 1;
                } else {
                    valid += 1;
                }
            }

            black_box(valid);
            black_box(invalid);
        })
    }

    #[bench]
    fn bench_equality_many(b: &mut Bencher) {
        let f = File::open("tests/benchmark-names.txt").ok().unwrap();
        let reader = BufReader::new(f);
        let names: Vec<Name> = reader.lines()
                                     .filter_map(|l| Name::parse(&l.ok().unwrap()))
                                     .collect();

        let mut deduped = HashSet::with_capacity(names.len() / 4);
        b.iter(|| {
            deduped.extend(names.iter());
            black_box(deduped.len())
        })
    }
}
<|endoftext|>


<commit_before>// This file is part of sokoban-rs
// Copyright 2015 Sébastien Watteau
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! This is an implementation of Sokoban in Rust.

#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate clap;
extern crate sdl2;
extern crate xml;

use clap::App;
use sdl2::event::Event;
use sdl2::image::LoadTexture;
use sdl2::image::INIT_PNG;
use sdl2::keyboard::Keycode;
use sdl2::render::{Canvas, TextureCreator};
use sdl2::video::{Window, WindowContext};
use sdl2::Sdl;
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
use std::str::FromStr;
use xml::reader::EventReader;
use xml::reader::XmlEvent;

pub mod error;
pub mod game;
pub mod painter;
pub mod shadow;
pub mod tileset;

use game::Level;
use painter::Painter;
use tileset::Tileset;

pub fn main() -> Result<(), Box<std::error::Error>> {
    // Read command line arguments
    let yml = load_yaml!("clap.yml");
    let matches = App::from_yaml(yml).get_matches();
    let width = value_t!(matches.value_of("width"), u32).unwrap_or(1024);
    let height = value_t!(matches.value_of("height"), u32).unwrap_or(768);
    let fullscreen = matches.is_present("fullscreen");
    let slc_file = matches.value_of("slc_file").unwrap();

    let collection = load_slc_file(slc_file)?;

    let sdl_context = sdl2::init()?;

    let _ = sdl2::image::init(INIT_PNG).unwrap();
    let ttf_context = sdl2::ttf::init().unwrap();

    let window = get_window(&sdl_context, width, height, fullscreen);

    let mut canvas = window
        .into_canvas()
        .build()
        .unwrap_or_else(|err| panic!("Failed to get an SDL canvas for the main window: {}", err));

    let texture_creator = canvas.texture_creator();

    let painter = {
        let big_set = load_tileset(
            &texture_creator,
            "assets/image/tileset.png",
            101,
            171,
            83,
            40,
        );
        let small_set = load_tileset(
            &texture_creator,
            "assets/image/tileset-small.png",
            50,
            85,
            41,
            20,
        );
        Painter::new(&mut canvas, big_set, small_set, &ttf_context)
    };

    mainloop(&sdl_context, &collection, painter, &mut canvas);

    Ok(())
}

fn mainloop(
    sdl_context: &Sdl,
    levels: &[Level],
    mut painter: Painter,
    canvas: &mut Canvas<Window>,
) {
    let mut collection = levels.into_iter();
    let mut reference_level = collection.next().unwrap();
    let mut level = reference_level.clone();

    let mut running = true;
    let mut event_pump = sdl_context.event_pump().unwrap();
    let mut skip = false;
    while running {
        if level.is_completed() || skip {
            match collection.next() {
                Some(l) => {
                    level = l.clone();
                    reference_level = l;
                    skip = false;
                }
                None => {
                    break;
                }
            }
        }
        painter.paint(canvas, &level);

        match event_pump.wait_event() {
            Event::Quit { .. }
            | Event::KeyDown {
                keycode: Some(Keycode::Escape),
                ..
            } => running = false,
            Event::KeyDown {
                keycode: Some(Keycode::Left),
                ..
            } => {
                level.step(game::Direction::Left);
            }
            Event::KeyDown {
                keycode: Some(Keycode::Right),
                ..
            } => {
                level.step(game::Direction::Right);
            }
            Event::KeyDown {
                keycode: Some(Keycode::Up),
                ..
            } => {
                level.step(game::Direction::Up);
            }
            Event::KeyDown {
                keycode: Some(Keycode::Down),
                ..
            } => {
                level.step(game::Direction::Down);
            }
            Event::KeyDown {
                keycode: Some(Keycode::R),
                ..
            } => {
                level = reference_level.clone();
            }
            Event::KeyDown {
                keycode: Some(Keycode::N),
                ..
            } => {
                skip = true;
            }
            _ => {}
        }
    }
}

fn get_window(sdl_context: &Sdl, width: u32, height: u32, fullscreen: bool) -> Window {

    let video_subsystem = sdl_context
        .video()
        .unwrap_or_else(|err| panic!("Failed to initialize the video subsystem: {}", err));

    let mut window_builder = video_subsystem.window("sokoban-rs", width, height);
    if fullscreen {
        window_builder.fullscreen();
    } else {
        window_builder.position_centered();
    }

    let window = window_builder
        .opengl()
        .build()
        .unwrap_or_else(|err| panic!("Failed to create the window: {}", err));

    window
}

fn load_tileset<P: AsRef<Path>>(
    texture_creator: &TextureCreator<WindowContext>,
    path: P,
    width: u32,
    height: u32,
    effective_height: u32,
    offset: i32,
) -> Tileset {
    let texture = texture_creator.load_texture(path.as_ref()).unwrap();
    Tileset::new(texture, width, height, effective_height, offset)
}

/// Builds levels from a level collection file in the SLC format.
fn load_slc_file<P: AsRef<Path>>(path: P) -> Result<Vec<Level>, error::SokobanError> {
    let mut collection = Vec::new();
    let file = try!(File::open(path.as_ref()));
    let reader = BufReader::new(file);
    let parser = EventReader::new(reader);

    let mut level_title = String::new();
    let mut level_str = String::new();
    let mut reading_level = false;
    for e in parser {
        match e {
            Ok(XmlEvent::StartElement {
                ref name,
                ref attributes,
                ..
            }) => {
                if name.local_name == "L" {
                    reading_level = true;
                } else if name.local_name == "Level" {
                    if let Some(id) = attributes.iter().find(|&attr| attr.name.local_name == "Id") {
                        level_title = id.value.clone();
                    }
                }
            }
            Ok(XmlEvent::EndElement { name }) => {
                if name.local_name == "Level" {
                    let mut level = try!(Level::from_str(&level_str));
                    level.set_title(level_title.clone());
                    collection.push(level);
                    level_str.clear();
                }
            }
            Ok(XmlEvent::Characters(ref data)) => {
                if reading_level {
                    level_str.push_str(data);
                    level_str.push('\n');
                }
            }
            _ => {}
        }
    }

    Ok(collection)
}
<commit_msg>More cleanup<commit_after>// This file is part of sokoban-rs
// Copyright 2015 Sébastien Watteau
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! This is an implementation of Sokoban in Rust.

#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate clap;
extern crate sdl2;
extern crate xml;

use clap::App;
use sdl2::event::Event;
use sdl2::image::LoadTexture;
use sdl2::image::INIT_PNG;
use sdl2::keyboard::Keycode;
use sdl2::render::{Canvas, TextureCreator};
use sdl2::video::{Window, WindowContext};
use sdl2::Sdl;
use std::error::Error;
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
use std::str::FromStr;
use xml::reader::EventReader;
use xml::reader::XmlEvent;

pub mod error;
pub mod game;
pub mod painter;
pub mod shadow;
pub mod tileset;

use game::{Direction, Level};
use painter::Painter;
use tileset::Tileset;

pub fn main() -> Result<(), Box<Error>> {
    // Read command line arguments
    let yml = load_yaml!("clap.yml");
    let matches = App::from_yaml(yml).get_matches();
    let width = value_t!(matches.value_of("width"), u32).unwrap_or(1024);
    let height = value_t!(matches.value_of("height"), u32).unwrap_or(768);
    let fullscreen = matches.is_present("fullscreen");
    let slc_file = matches.value_of("slc_file").unwrap();

    // Load the level collection file
    let levels = load_slc_file(slc_file)?;

    // Initialize SDL components
    let sdl = sdl2::init()?;
    let _ = sdl2::image::init(INIT_PNG)?;
    let ttf_context = sdl2::ttf::init()?;

    let window = create_window(&sdl, width, height, fullscreen)?;
    let mut canvas = window.into_canvas().build()?;
    let texture_creator = canvas.texture_creator();

    let mut painter = {
        let big_set = load_tileset(
            &texture_creator,
            "assets/image/tileset.png",
            101,
            171,
            83,
            40,
        )?;
        let small_set = load_tileset(
            &texture_creator,
            "assets/image/tileset-small.png",
            50,
            85,
            41,
            20,
        )?;
        Painter::new(&mut canvas, big_set, small_set, &ttf_context)
    };

    mainloop(&sdl, &levels, &mut painter, &mut canvas);

    Ok(())
}

/// Builds levels from a level collection file in the SLC format.
fn load_slc_file<P: AsRef<Path>>(path: P) -> Result<Vec<Level>, error::SokobanError> {
    let mut levels = Vec::new();

    let parser = {
        let file = File::open(path.as_ref())?;
        EventReader::new(BufReader::new(file))
    };

    let mut level_title = String::new();
    let mut level_data = String::new();
    let mut reading_level = false;
    for event in parser {
        match event {
            Ok(XmlEvent::StartElement {
                ref name,
                ref attributes,
                ..
            }) => {
                if name.local_name == "L" {
                    reading_level = true;
                } else if name.local_name == "Level" {
                    if let Some(id) = attributes.iter().find(|&attr| attr.name.local_name == "Id") {
                        level_title = id.value.clone();
                    }
                }
            }
            Ok(XmlEvent::EndElement { name }) => {
                if name.local_name == "Level" {
                    let mut level = Level::from_str(&level_data)?;
                    level.set_title(level_title.clone());
                    levels.push(level);
                    level_data.clear();
                }
            }
            Ok(XmlEvent::Characters(ref data)) => {
                if reading_level {
                    level_data.push_str(data);
                    level_data.push('\n');
                }
            }
            _ => {}
        }
    }

    Ok(levels)
}

/// Creates the SDL window
fn create_window(sdl: &Sdl, width: u32, height: u32, fullscreen: bool) -> Result<Window, Box<Error>> {
    let mut window_builder = sdl.video()?.window("sokoban-rs", width, height);
    if fullscreen {
        window_builder.fullscreen();
    } else {
        window_builder.position_centered();
    }
    let window = window_builder.opengl().build()?;
    Ok(window)
}

/// Loads a tileset
fn load_tileset<P: AsRef<Path>>(
    texture_creator: &TextureCreator<WindowContext>,
    path: P,
    width: u32,
    height: u32,
    effective_height: u32,
    offset: i32,
) -> Result<Tileset, Box<Error>> {
    let texture = texture_creator.load_texture(path.as_ref())?;
    let tileset = Tileset::new(texture, width, height, effective_height, offset);
    Ok(tileset)
}

/// Main game event loop
fn mainloop(
    sdl: &Sdl,
    levels: &[Level],
    painter: &mut Painter,
    canvas: &mut Canvas<Window>,
) {
    let mut collection = levels.into_iter();
    let mut reference_level = collection.next().unwrap();
    let mut level = reference_level.clone();

    let mut running = true;
    let mut event_pump = sdl.event_pump().unwrap();
    let mut skip = false;
    while running {
        if level.is_completed() || skip {
            match collection.next() {
                Some(l) => {
                    level = l.clone();
                    reference_level = l;
                    skip = false;
                }
                None => {
                    break;
                }
            }
        }
        painter.paint(canvas, &level);

        match event_pump.wait_event() {
            Event::Quit { .. }
            | Event::KeyDown {
                keycode: Some(Keycode::Escape),
                ..
            } => running = false,
            Event::KeyDown {
                keycode: Some(Keycode::Left),
                ..
            } => {
                level.step(Direction::Left);
            }
            Event::KeyDown {
                keycode: Some(Keycode::Right),
                ..
            } => {
                level.step(Direction::Right);
            }
            Event::KeyDown {
                keycode: Some(Keycode::Up),
                ..
            } => {
                level.step(Direction::Up);
            }
            Event::KeyDown {
                keycode: Some(Keycode::Down),
                ..
            } => {
                level.step(Direction::Down);
            }
            Event::KeyDown {
                keycode: Some(Keycode::R),
                ..
            } => {
                level = reference_level.clone();
            }
            Event::KeyDown {
                keycode: Some(Keycode::N),
                ..
            } => {
                skip = true;
            }
            _ => {}
        }
    }
}
<|endoftext|>