<commit_before>extern crate hyper;
extern crate rustc_serialize;
extern crate chrono;
extern crate regex;
extern crate env_logger;
#[macro_use] extern crate log;
extern crate docopt;

use std::ascii::AsciiExt;
use std::io::Read;
use std::env;
use std::collections::HashMap;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::fmt;
use std::error::Error;
use std::result;

use chrono::naive::datetime::NaiveDateTime;
use chrono::offset::local::Local;
use chrono::offset::fixed::FixedOffset;
use chrono::datetime::DateTime;
use chrono::Datelike;

use rustc_serialize::json;
use rustc_serialize::Decodable;
use hyper::client::Client;

use regex::Regex;

use docopt::Docopt;

static USAGE: &'static str = "
Washing State Ferry Schedules

Usage:
  wsf [options] <from> <to>
  wsf -h

  <from> and <to> are a prefix of the departing terminal and arriving
  terminal, respectively. For example 'wsf sea ba' is equivalent to
  'wsf Seattle \"Bainbridge Island\"'.

Options:
  -h --help     Show this screen.
";

#[derive(Debug, RustcDecodable)]
struct Args {
    arg_from: String,
    arg_to: String,
}

fn run() -> Result<()> {
    try!(env_logger::init());

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

    let from_in: &str = &args.arg_from.to_ascii_lowercase();
    let to_in: &str   = &args.arg_to.to_ascii_lowercase();

    // pull in api key at *build* time from environment
    let mut s = Session::new(env!("WSDOT_API_KEY"));

    let now = Local::now();
    let mut from: Option<i32> = None;
    let mut to: Option<i32> = None;
    for terminal in try!(s.terminals()).iter() {
        if terminal.Description.to_ascii_lowercase().starts_with(&from_in) {
            from = Some(terminal.TerminalID);
        }
        if terminal.Description.to_ascii_lowercase().starts_with(&to_in) {
            to = Some(terminal.TerminalID);
        }
    }
    let from = try!(from.ok_or(CliError::BadInput(format!("'{}' is not a known port!", from_in))));
    let to = try!(to.ok_or(CliError::BadInput(format!("'{}' is not a known port!", to_in))));
    let tc = try!(s.schedule(from, to));
    for time in tc.Times.iter() {
        if time.depart_time() > now {
            println!("{}\t{}\t{}\t{}",
                     time.depart_time().time(),
                     tc.DepartingTerminalName,
                     tc.ArrivingTerminalName,
                     time.VesselName );
        }
    }
    s.save_cache()
}

fn main() {
    match run() {
        Ok(_) => {},
        Err(e) => {
            // TODO print to STDERR instead of STDOUT
            println!("{}", e);
            std::process::exit(1);
        }
    }
}

struct Session {
    api_key: String,
    client: Client,
    cache: Cache,
    cacheflushdate: String,
    cache_path: String,
    offline: bool,
}

impl Session {

    fn new(api_key: &str) -> Session {
        let mut cache_path: PathBuf = env::home_dir().unwrap();
        cache_path.push(".wsf.cache");
        let cache_path = format!("{}", cache_path.display());

        let mut s = Session {
            api_key: api_key.to_string(),
            client: Client::new(),
            cache: Cache::load(&cache_path).unwrap_or(Cache::empty()),
            cacheflushdate: String::new(),
            cache_path: cache_path,
            offline: false,
        };

        // TODO this is kind of gross, cfd as optional to indicate offline, maybe?
        s.offline = match s.get::<String>("/cacheflushdate".to_owned()) {
            Ok(cfd) => {
                    s.cacheflushdate = cfd;
                    false
                },
            Err(_) => true,
        };
        return s;
    }

    fn save_cache(&mut self) -> Result<()> {
        self.cache.cache_flush_date = self.cacheflushdate.clone();

        let mut f = try!(File::create(&self.cache_path));
        let encoded = try!(json::encode(&self.cache));
        Ok(try!(f.write_all(encoded.as_bytes())))
    }

    fn get<T: Decodable>(&self, path: String) -> Result<T> {
        let url = &format!("http://www.wsdot.wa.gov/ferries/api/schedule/rest{}?apiaccesscode={}",
                            path,
                            self.api_key);
        let mut res = try!(self.client.get(url).send());
        assert_eq!(res.status, hyper::Ok);

        let mut buf = String::new();
        try!(res.read_to_string(&mut buf));
        Ok(try!(json::decode::<T>(&buf)))
    }

    fn terminals(&mut self) -> Result<Vec<Terminal>> {
        if self.offline || (self.cache.cache_flush_date == self.cacheflushdate) {
            return Ok(self.cache.terminals.clone())
        }
        else {
            let now = Local::today();
            let path = format!("/terminals/{}-{}-{}", now.year(), now.month(), now.day());
            let routes: Vec<Terminal> = try!(self.get(path));
            self.cache.terminals = routes.clone();
            return Ok(routes);
        }
    }

    fn schedule(&mut self, from: i32, to: i32) -> Result<TerminalCombo> {
        let mut cache_is_stale = true;
        let cache_key = format!("{} {}", from, to);

        if self.offline || (self.cache.cache_flush_date == self.cacheflushdate) {
            if self.cache.sailings.contains_key(&cache_key) {
                // cache is up to date and has route!
                return Ok(self.cache.sailings.get(&cache_key)
                                              .expect("checked for key in cache then not found")
                                              .clone());
            }
            else {
                // cache is up to date, but we don't have this route in it
                cache_is_stale = false;
            }
        }

        if cache_is_stale {
            self.cache.sailings.clear();
        }

        let now = Local::now();
        let path = format!("/schedule/{}-{}-{}/{}/{}",
                            now.year(), now.month(), now.day(), from, to);

        let schedule: Schedule = try!(self.get(path));

        self.cache.sailings.insert(cache_key, schedule.TerminalCombos[0].clone());
        Ok(schedule.TerminalCombos[0].clone())
    }
}

#[derive(RustcDecodable, RustcEncodable, Debug)]
struct Cache {
    terminals: Vec<Terminal>,
    sailings: HashMap<String, TerminalCombo>,
    cache_flush_date: String,
}

impl Cache {

    fn empty() -> Cache {
        Cache {
            terminals: vec![],
            sailings: HashMap::new(),
            cache_flush_date: String::new(),
        }
    }

    fn load(path: &String) -> Result<Cache> {
        let mut f = try!(File::open(path));
        let mut s = String::new();
        try!(f.read_to_string(&mut s));
        let cache: Cache = try!(json::decode(&s));
        Ok(cache)
    }
}

#[allow(non_snake_case)]
#[derive(RustcDecodable, RustcEncodable, Debug, Clone)]
struct Terminal {
    TerminalID: i32,
    Description: String,
}

#[allow(non_snake_case)]
#[derive(RustcDecodable, RustcEncodable, Clone, Debug)]
struct SailingTime {
    DepartingTime: String,
    ArrivingTime: Option<String>,
    VesselName: String,
}

impl SailingTime {
    // parse date strings of form "/Date(1436318400000-0700)/"
    fn depart_time(&self) -> DateTime<Local> {
        let re = Regex::new(r"^/Date\((\d{10})000-(\d{2})(\d{2})\)/$").unwrap();
        let caps = re.captures(&self.DepartingTime).unwrap();

        let epoch: i64 = caps.at(1).unwrap().parse().unwrap();
        let tz_hours: i32 = caps.at(2).unwrap().parse().unwrap();
        let tz_minutes: i32 = caps.at(3).unwrap().parse().unwrap();

        let nd = NaiveDateTime::from_timestamp(epoch, 0);
        let tz = FixedOffset::west((tz_hours * 3600) + (tz_minutes * 60));
        let fotz: DateTime<FixedOffset> = DateTime::from_utc(nd, tz);
        fotz.with_timezone(&Local)
    }
}

#[allow(non_snake_case)]
#[derive(RustcDecodable, RustcEncodable, Clone, Debug)]
struct TerminalCombo {
    Times: Vec<SailingTime>,
    DepartingTerminalName: String,
    ArrivingTerminalName: String,
}

#[allow(non_snake_case)]
#[derive(RustcDecodable, RustcEncodable, Debug)]
struct Schedule {
    TerminalCombos: Vec<TerminalCombo>,
}

type Result<T> = result::Result<T, CliError>;

#[derive(Debug)]
enum CliError {
    Log(log::SetLoggerError),
    Parse(rustc_serialize::json::DecoderError),
    SaveCache(rustc_serialize::json::EncoderError),
    Http(hyper::error::Error),
    Io(std::io::Error),
    BadInput(String),
}

impl fmt::Display for CliError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            CliError::Log(ref err) => format!("Unable to configure logging: {}", err).fmt(f),
            CliError::Parse(ref err) => format!("Unable to parse WSDOT Data: {}", err).fmt(f),
            CliError::SaveCache(ref err) => format!("Unable to save cache: {}", err).fmt(f),
            CliError::Http(ref err) => format!("Unable to communicate with WSDOT: {}", err).fmt(f),
            CliError::Io(ref err) => format!("Unable to read data: {}", err).fmt(f),
            CliError::BadInput(ref desc) => format!("Unable to understand input: {}", desc).fmt(f),
        }
    }
}

impl Error for CliError {
    fn description(&self) -> &str {
        match *self {
            CliError::BadInput(ref err) => err,
            _ => self.description(),
        }
    }
}

impl From<rustc_serialize::json::EncoderError> for CliError {
    fn from(err: rustc_serialize::json::EncoderError) -> CliError {
        CliError::SaveCache(err)
    }
}

impl From<log::SetLoggerError> for CliError {
    fn from(err: log::SetLoggerError) -> CliError {
        CliError::Log(err)
    }
}
impl From<hyper::error::Error> for CliError {
    fn from(err: hyper::error::Error) -> CliError {
        CliError::Http(err)
    }
}

impl From<std::io::Error> for CliError {
    fn from(err: std::io::Error) -> CliError {
        CliError::Io(err)
    }
}

impl From<rustc_serialize::json::DecoderError> for CliError {
    fn from(err: rustc_serialize::json::DecoderError) -> CliError {
        CliError::Parse(err)
    }
}
<commit_msg>remove macro use annotation from log, not actually using the macros!<commit_after>extern crate hyper;
extern crate rustc_serialize;
extern crate chrono;
extern crate regex;
extern crate env_logger;
extern crate log;
extern crate docopt;

use std::ascii::AsciiExt;
use std::io::Read;
use std::env;
use std::collections::HashMap;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::fmt;
use std::error::Error;
use std::result;

use chrono::naive::datetime::NaiveDateTime;
use chrono::offset::local::Local;
use chrono::offset::fixed::FixedOffset;
use chrono::datetime::DateTime;
use chrono::Datelike;

use rustc_serialize::json;
use rustc_serialize::Decodable;
use hyper::client::Client;

use regex::Regex;

use docopt::Docopt;

static USAGE: &'static str = "
Washing State Ferry Schedules

Usage:
  wsf [options] <from> <to>
  wsf -h

  <from> and <to> are a prefix of the departing terminal and arriving
  terminal, respectively. For example 'wsf sea ba' is equivalent to
  'wsf Seattle \"Bainbridge Island\"'.

Options:
  -h --help     Show this screen.
";

#[derive(Debug, RustcDecodable)]
struct Args {
    arg_from: String,
    arg_to: String,
}

fn run() -> Result<()> {
    try!(env_logger::init());

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

    let from_in: &str = &args.arg_from.to_ascii_lowercase();
    let to_in: &str   = &args.arg_to.to_ascii_lowercase();

    // pull in api key at *build* time from environment
    let mut s = Session::new(env!("WSDOT_API_KEY"));

    let now = Local::now();
    let mut from: Option<i32> = None;
    let mut to: Option<i32> = None;
    for terminal in try!(s.terminals()).iter() {
        if terminal.Description.to_ascii_lowercase().starts_with(&from_in) {
            from = Some(terminal.TerminalID);
        }
        if terminal.Description.to_ascii_lowercase().starts_with(&to_in) {
            to = Some(terminal.TerminalID);
        }
    }
    let from = try!(from.ok_or(CliError::BadInput(format!("'{}' is not a known port!", from_in))));
    let to = try!(to.ok_or(CliError::BadInput(format!("'{}' is not a known port!", to_in))));
    let tc = try!(s.schedule(from, to));
    for time in tc.Times.iter() {
        if time.depart_time() > now {
            println!("{}\t{}\t{}\t{}",
                     time.depart_time().time(),
                     tc.DepartingTerminalName,
                     tc.ArrivingTerminalName,
                     time.VesselName );
        }
    }
    s.save_cache()
}

fn main() {
    match run() {
        Ok(_) => {},
        Err(e) => {
            // TODO print to STDERR instead of STDOUT
            println!("{}", e);
            std::process::exit(1);
        }
    }
}

struct Session {
    api_key: String,
    client: Client,
    cache: Cache,
    cacheflushdate: String,
    cache_path: String,
    offline: bool,
}

impl Session {

    fn new(api_key: &str) -> Session {
        let mut cache_path: PathBuf = env::home_dir().unwrap();
        cache_path.push(".wsf.cache");
        let cache_path = format!("{}", cache_path.display());

        let mut s = Session {
            api_key: api_key.to_string(),
            client: Client::new(),
            cache: Cache::load(&cache_path).unwrap_or(Cache::empty()),
            cacheflushdate: String::new(),
            cache_path: cache_path,
            offline: false,
        };

        // TODO this is kind of gross, cfd as optional to indicate offline, maybe?
        s.offline = match s.get::<String>("/cacheflushdate".to_owned()) {
            Ok(cfd) => {
                    s.cacheflushdate = cfd;
                    false
                },
            Err(_) => true,
        };
        return s;
    }

    fn save_cache(&mut self) -> Result<()> {
        self.cache.cache_flush_date = self.cacheflushdate.clone();

        let mut f = try!(File::create(&self.cache_path));
        let encoded = try!(json::encode(&self.cache));
        Ok(try!(f.write_all(encoded.as_bytes())))
    }

    fn get<T: Decodable>(&self, path: String) -> Result<T> {
        let url = &format!("http://www.wsdot.wa.gov/ferries/api/schedule/rest{}?apiaccesscode={}",
                            path,
                            self.api_key);
        let mut res = try!(self.client.get(url).send());
        assert_eq!(res.status, hyper::Ok);

        let mut buf = String::new();
        try!(res.read_to_string(&mut buf));
        Ok(try!(json::decode::<T>(&buf)))
    }

    fn terminals(&mut self) -> Result<Vec<Terminal>> {
        if self.offline || (self.cache.cache_flush_date == self.cacheflushdate) {
            return Ok(self.cache.terminals.clone())
        }
        else {
            let now = Local::today();
            let path = format!("/terminals/{}-{}-{}", now.year(), now.month(), now.day());
            let routes: Vec<Terminal> = try!(self.get(path));
            self.cache.terminals = routes.clone();
            return Ok(routes);
        }
    }

    fn schedule(&mut self, from: i32, to: i32) -> Result<TerminalCombo> {
        let mut cache_is_stale = true;
        let cache_key = format!("{} {}", from, to);

        if self.offline || (self.cache.cache_flush_date == self.cacheflushdate) {
            if self.cache.sailings.contains_key(&cache_key) {
                // cache is up to date and has route!
                return Ok(self.cache.sailings.get(&cache_key)
                                              .expect("checked for key in cache then not found")
                                              .clone());
            }
            else {
                // cache is up to date, but we don't have this route in it
                cache_is_stale = false;
            }
        }

        if cache_is_stale {
            self.cache.sailings.clear();
        }

        let now = Local::now();
        let path = format!("/schedule/{}-{}-{}/{}/{}",
                            now.year(), now.month(), now.day(), from, to);

        let schedule: Schedule = try!(self.get(path));

        self.cache.sailings.insert(cache_key, schedule.TerminalCombos[0].clone());
        Ok(schedule.TerminalCombos[0].clone())
    }
}

#[derive(RustcDecodable, RustcEncodable, Debug)]
struct Cache {
    terminals: Vec<Terminal>,
    sailings: HashMap<String, TerminalCombo>,
    cache_flush_date: String,
}

impl Cache {

    fn empty() -> Cache {
        Cache {
            terminals: vec![],
            sailings: HashMap::new(),
            cache_flush_date: String::new(),
        }
    }

    fn load(path: &String) -> Result<Cache> {
        let mut f = try!(File::open(path));
        let mut s = String::new();
        try!(f.read_to_string(&mut s));
        let cache: Cache = try!(json::decode(&s));
        Ok(cache)
    }
}

#[allow(non_snake_case)]
#[derive(RustcDecodable, RustcEncodable, Debug, Clone)]
struct Terminal {
    TerminalID: i32,
    Description: String,
}

#[allow(non_snake_case)]
#[derive(RustcDecodable, RustcEncodable, Clone, Debug)]
struct SailingTime {
    DepartingTime: String,
    ArrivingTime: Option<String>,
    VesselName: String,
}

impl SailingTime {
    // parse date strings of form "/Date(1436318400000-0700)/"
    fn depart_time(&self) -> DateTime<Local> {
        let re = Regex::new(r"^/Date\((\d{10})000-(\d{2})(\d{2})\)/$").unwrap();
        let caps = re.captures(&self.DepartingTime).unwrap();

        let epoch: i64 = caps.at(1).unwrap().parse().unwrap();
        let tz_hours: i32 = caps.at(2).unwrap().parse().unwrap();
        let tz_minutes: i32 = caps.at(3).unwrap().parse().unwrap();

        let nd = NaiveDateTime::from_timestamp(epoch, 0);
        let tz = FixedOffset::west((tz_hours * 3600) + (tz_minutes * 60));
        let fotz: DateTime<FixedOffset> = DateTime::from_utc(nd, tz);
        fotz.with_timezone(&Local)
    }
}

#[allow(non_snake_case)]
#[derive(RustcDecodable, RustcEncodable, Clone, Debug)]
struct TerminalCombo {
    Times: Vec<SailingTime>,
    DepartingTerminalName: String,
    ArrivingTerminalName: String,
}

#[allow(non_snake_case)]
#[derive(RustcDecodable, RustcEncodable, Debug)]
struct Schedule {
    TerminalCombos: Vec<TerminalCombo>,
}

type Result<T> = result::Result<T, CliError>;

#[derive(Debug)]
enum CliError {
    Log(log::SetLoggerError),
    Parse(rustc_serialize::json::DecoderError),
    SaveCache(rustc_serialize::json::EncoderError),
    Http(hyper::error::Error),
    Io(std::io::Error),
    BadInput(String),
}

impl fmt::Display for CliError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            CliError::Log(ref err) => format!("Unable to configure logging: {}", err).fmt(f),
            CliError::Parse(ref err) => format!("Unable to parse WSDOT Data: {}", err).fmt(f),
            CliError::SaveCache(ref err) => format!("Unable to save cache: {}", err).fmt(f),
            CliError::Http(ref err) => format!("Unable to communicate with WSDOT: {}", err).fmt(f),
            CliError::Io(ref err) => format!("Unable to read data: {}", err).fmt(f),
            CliError::BadInput(ref desc) => format!("Unable to understand input: {}", desc).fmt(f),
        }
    }
}

impl Error for CliError {
    fn description(&self) -> &str {
        match *self {
            CliError::BadInput(ref err) => err,
            _ => self.description(),
        }
    }
}

impl From<rustc_serialize::json::EncoderError> for CliError {
    fn from(err: rustc_serialize::json::EncoderError) -> CliError {
        CliError::SaveCache(err)
    }
}

impl From<log::SetLoggerError> for CliError {
    fn from(err: log::SetLoggerError) -> CliError {
        CliError::Log(err)
    }
}
impl From<hyper::error::Error> for CliError {
    fn from(err: hyper::error::Error) -> CliError {
        CliError::Http(err)
    }
}

impl From<std::io::Error> for CliError {
    fn from(err: std::io::Error) -> CliError {
        CliError::Io(err)
    }
}

impl From<rustc_serialize::json::DecoderError> for CliError {
    fn from(err: rustc_serialize::json::DecoderError) -> CliError {
        CliError::Parse(err)
    }
}
<|endoftext|>


<commit_before>extern crate iron;

use iron::mime::Mime;
use iron::prelude::*;
use iron::status;

fn main() {
    Iron::new(|&: _: &mut Request| {
        let content_type: Mime = "text/html".parse().unwrap();
        Ok(Response::with((status::Ok, "Hello Rust Warsaw!")).set(content_type))
    }).listen("localhost:3000").unwrap();
}
<commit_msg>Port number as integer.<commit_after>extern crate iron;

use std::io::net::ip::Ipv4Addr;

use iron::mime::Mime;
use iron::prelude::*;
use iron::status;

fn main() {
    let port = 3000;
    Iron::new(|&: _: &mut Request| {
        let content_type: Mime = "text/html".parse().unwrap();
        Ok(Response::with((status::Ok, "Hello Rust Warsaw!")).set(content_type))
    }).listen((Ipv4Addr(127, 0, 0, 1), port)).unwrap();
}
<|endoftext|>


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

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;

#[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),
    };

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

    let mut show_file = cache::readfile(String::from("data/6360523/show_1.json"));
    let mut show_item: ShowItem = json::decode(&show_file).unwrap();

    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;
        }

        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 => {
                                state = Status::Show;
                            }
                            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 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>refactor data stores<commit_after>extern crate hkg;
extern crate rustbox;
extern crate rustc_serialize;
extern crate chrono;

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;

#[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),
    };

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

    let mut show_file = cache::readfile(String::from("data/6360523/show_1.json"));
    let mut show_item: ShowItem = json::decode(&show_file).unwrap();

    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;
        }

        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 => {
                                state = Status::Show;
                            }
                            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 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>extern crate loc;

extern crate clap;
extern crate deque;
extern crate itertools;
extern crate num_cpus;
extern crate walkdir;
extern crate regex;

use clap::{Arg, App, AppSettings};
use walkdir::WalkDir;

use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::thread;

use deque::{Stealer, Stolen};
use regex::Regex;

use loc::*;

enum Work {
    File(String),
    Quit,
}

struct Worker {
    chan: Stealer<Work>,
}

#[derive(Clone)]
struct FileCount {
    path: String,
    lang: Lang,
    count: Count,
}

impl Worker {
    fn run(self) -> Vec<FileCount> {
        let mut v: Vec<FileCount> = vec![];
        loop {
            match self.chan.steal() {
                // What causes these?
                Stolen::Empty | Stolen::Abort => continue,
                Stolen::Data(Work::Quit) => break,
                Stolen::Data(Work::File(path)) => {
                    let lang = lang_from_ext(&path);
                    if lang != Lang::Unrecognized {
                        let count = count(&path);
                        v.push(FileCount {
                            lang: lang,
                            path: String::from(path),
                            count: count,
                        });
                    }
                }
            };
        }
        v
    }
}

fn main() {

    let matches = App::new("count")
        .global_settings(&[AppSettings::ColoredHelp])
        .version("0.1")
        .author("Curtis Gagliardi <curtis@curtis.io>")
        .about("counts things quickly hopefully")
        // TODO(cgag): actually implement filtering
        .arg(Arg::with_name("exclude")
            .required(false)
            .long("exclude")
            .value_name("REGEX")
            .takes_value(true)
            .help("comma separated list of files/directories to ignore"))
        .arg(Arg::with_name("files")
             .required(false)
             .long("files")
             .takes_value(false)
             .help("Show stats for individual files"))
        .arg(Arg::with_name("sort")
            .required(false)
            .long("sort")
            .value_name("COLUMN")
            .takes_value(true)
            .help("Column to sort by"))
        .arg(Arg::with_name("target")
            .required(true)
            .help("File or directory to count"))
        .get_matches();

    let filepaths = matches.values_of("target").unwrap();
    let sort = matches.value_of("sort").unwrap_or("code");
    let by_file = matches.is_present("files");
    let exclude_regex = match matches.value_of("exclude") {
        Some(rx_str) => {
            match Regex::new(rx_str) {
                Ok(r) => Some(r),
                Err(e) => {
                    println!("Error processing regex: {}", e);
                    std::process::exit(1);
                }
            }
        }
        None => None,
    };

    let threads = num_cpus::get();
    let mut workers = vec![];
    let (workq, stealer) = deque::new();
    for _ in 0..threads {
        let worker = Worker { chan: stealer.clone() };
        workers.push(thread::spawn(|| worker.run()));
    }

    for filepath in filepaths {
        // TODO(cgag): cleanup?
        if exclude_regex.is_some() {
            let r = &&exclude_regex.clone().unwrap();
            for entry in WalkDir::new(filepath).into_iter().filter_map(|e| e.ok()) {
                if entry.file_type().is_file() {
                    let path = entry.path().to_str().unwrap();
                    if !r.is_match(path) {
                        workq.push(Work::File(String::from(path)));
                    }
                }
            }
        } else {
            for entry in WalkDir::new(filepath).into_iter().filter_map(|e| e.ok()) {
                if entry.file_type().is_file() {
                    let path = entry.path().to_str().unwrap();
                    workq.push(Work::File(String::from(path)));
                }
            }
        }
    }

    for _ in 0..workers.len() {
        workq.push(Work::Quit);
    }

    let mut filecounts: Vec<FileCount> = Vec::new();
    for worker in workers {
        filecounts.extend(worker.join().unwrap().iter().cloned())
    }

    let mut by_lang: HashMap<Lang, Vec<FileCount>> = HashMap::new();
    for fc in filecounts {
        match by_lang.entry(fc.lang) {
            Entry::Occupied(mut by_lang) => by_lang.get_mut().push(fc),
            Entry::Vacant(by_lang) => {
                by_lang.insert(vec![fc]);
            }
        };
    }

    let linesep = str_repeat("-", 81);

    if by_file {
        // TODO(cgag): Need sorting for by_file as well.
        println!("{}", linesep);
        println!(" {0: <18} {1: >8} {2: >12} {3: >12} {4: >12} {5: >12}",
                 "Language",
                 "Files",
                 "Lines",
                 "Blank",
                 "Comment",
                 "Code");
        println!("{}", linesep);

        for (lang, filecounts) in by_lang {
            let mut total = Count::default();
            for fc in &filecounts {
                total.merge(&fc.count);
            }

            println!("{}", linesep);
            println!(" {0: <18} {1: >8} {2: >12} {3: >12} {4: >12} {5: >12}",
                     lang,
                     filecounts.len(),
                     total.lines,
                     total.blank,
                     total.comment,
                     total.code);

            println!("{}", linesep);
            for fc in filecounts {
                println!("|{0: <25} {1: >12} {2: >12} {3: >12} {4: >12}",
                         last_n_chars(&fc.path, 25),
                         fc.count.lines,
                         fc.count.blank,
                         fc.count.comment,
                         fc.count.code);
            }
        }
    } else {
        let mut lang_totals: HashMap<&Lang, LangTotal> = HashMap::new();
        for (lang, filecounts) in &by_lang {
            let mut lang_total = Count::default();
            for fc in filecounts {
                lang_total.merge(&fc.count);
            }
            lang_totals.insert(lang,
                               LangTotal {
                                   files: filecounts.len() as u32,
                                   count: lang_total,
                               });
        }

        let mut totals_by_lang = lang_totals.iter().collect::<Vec<(&&Lang, &LangTotal)>>();
        match sort {
            "language" => totals_by_lang.sort_by(|&(l1, _), &(l2, _)| l1.to_s().cmp(l2.to_s())),
            "files" => totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.files.cmp(&c1.files)),
            "code" => {
                totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.count.code.cmp(&c1.count.code))
            }
            "comment" => {
                totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.count.comment.cmp(&c1.count.comment))
            }
            "blank" => {
                totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.count.blank.cmp(&c1.count.blank))
            }
            "lines" => {
                totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.count.lines.cmp(&c1.count.lines))
            }
            _ => {
                println!("invalid sort option {}, sorting by code", sort);
                totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.count.code.cmp(&c1.count.code))
            }
        }

        println!("{}", linesep);
        println!(" {0: <18} {1: >8} {2: >12} {3: >12} {4: >12} {5: >12}",
                 "Language",
                 "Files",
                 "Lines",
                 "Blank",
                 "Comment",
                 "Code");
        println!("{}", linesep);

        for &(lang, total) in &totals_by_lang {
            println!(" {0: <18} {1: >8} {2: >12} {3: >12} {4: >12} {5: >12}",
                     lang,
                     total.files,
                     total.count.lines,
                     total.count.blank,
                     total.count.comment,
                     total.count.code);
        }

        let mut totals = LangTotal {
            files: 0,
            count: Count::default(),
        };
        for &(_, total) in &totals_by_lang {
            totals.files += total.files;
            totals.count.code += total.count.code;
            totals.count.blank += total.count.blank;
            totals.count.comment += total.count.comment;
            totals.count.lines += total.count.lines;
        }

        println!("{}", linesep);
        println!(" {0: <18} {1: >8} {2: >12} {3: >12} {4: >12} {5: >12}",
                 "Total",
                 totals.files,
                 totals.count.lines,
                 totals.count.blank,
                 totals.count.comment,
                 totals.count.code);
        println!("{}", linesep);
    }
}

fn last_n_chars(s: &str, n: usize) -> String {
    if s.len() <= n {
        return String::from(s);
    }
    s.chars().skip(s.len() - n).collect::<String>()
}

fn str_repeat(s: &str, n: usize) -> String {
    std::iter::repeat(s).take(n).collect::<Vec<_>>().join("")
}
<commit_msg>ripgrep comment<commit_after>extern crate loc;

extern crate clap;
extern crate deque;
extern crate itertools;
extern crate num_cpus;
extern crate walkdir;
extern crate regex;

use clap::{Arg, App, AppSettings};
use walkdir::WalkDir;

use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::thread;

use deque::{Stealer, Stolen};
use regex::Regex;

use loc::*;

enum Work {
    File(String),
    Quit,
}

struct Worker {
    chan: Stealer<Work>,
}

#[derive(Clone)]
struct FileCount {
    path: String,
    lang: Lang,
    count: Count,
}

// This concurrency pattern ripped directly from ripgrep
impl Worker {
    fn run(self) -> Vec<FileCount> {
        let mut v: Vec<FileCount> = vec![];
        loop {
            match self.chan.steal() {
                // What causes these?
                Stolen::Empty | Stolen::Abort => continue,
                Stolen::Data(Work::Quit) => break,
                Stolen::Data(Work::File(path)) => {
                    let lang = lang_from_ext(&path);
                    if lang != Lang::Unrecognized {
                        let count = count(&path);
                        v.push(FileCount {
                            lang: lang,
                            path: String::from(path),
                            count: count,
                        });
                    }
                }
            };
        }
        v
    }
}

fn main() {

    let matches = App::new("count")
        .global_settings(&[AppSettings::ColoredHelp])
        .version("0.1")
        .author("Curtis Gagliardi <curtis@curtis.io>")
        .about("counts things quickly hopefully")
        // TODO(cgag): actually implement filtering
        .arg(Arg::with_name("exclude")
            .required(false)
            .long("exclude")
            .value_name("REGEX")
            .takes_value(true)
            .help("comma separated list of files/directories to ignore"))
        .arg(Arg::with_name("files")
             .required(false)
             .long("files")
             .takes_value(false)
             .help("Show stats for individual files"))
        .arg(Arg::with_name("sort")
            .required(false)
            .long("sort")
            .value_name("COLUMN")
            .takes_value(true)
            .help("Column to sort by"))
        .arg(Arg::with_name("target")
            .required(true)
            .help("File or directory to count"))
        .get_matches();

    let filepaths = matches.values_of("target").unwrap();
    let sort = matches.value_of("sort").unwrap_or("code");
    let by_file = matches.is_present("files");
    let exclude_regex = match matches.value_of("exclude") {
        Some(rx_str) => {
            match Regex::new(rx_str) {
                Ok(r) => Some(r),
                Err(e) => {
                    println!("Error processing regex: {}", e);
                    std::process::exit(1);
                }
            }
        }
        None => None,
    };

    let threads = num_cpus::get();
    let mut workers = vec![];
    let (workq, stealer) = deque::new();
    for _ in 0..threads {
        let worker = Worker { chan: stealer.clone() };
        workers.push(thread::spawn(|| worker.run()));
    }

    for filepath in filepaths {
        // TODO(cgag): cleanup?
        if exclude_regex.is_some() {
            let r = &&exclude_regex.clone().unwrap();
            for entry in WalkDir::new(filepath).into_iter().filter_map(|e| e.ok()) {
                if entry.file_type().is_file() {
                    let path = entry.path().to_str().unwrap();
                    if !r.is_match(path) {
                        workq.push(Work::File(String::from(path)));
                    }
                }
            }
        } else {
            for entry in WalkDir::new(filepath).into_iter().filter_map(|e| e.ok()) {
                if entry.file_type().is_file() {
                    let path = entry.path().to_str().unwrap();
                    workq.push(Work::File(String::from(path)));
                }
            }
        }
    }

    for _ in 0..workers.len() {
        workq.push(Work::Quit);
    }

    let mut filecounts: Vec<FileCount> = Vec::new();
    for worker in workers {
        filecounts.extend(worker.join().unwrap().iter().cloned())
    }

    let mut by_lang: HashMap<Lang, Vec<FileCount>> = HashMap::new();
    for fc in filecounts {
        match by_lang.entry(fc.lang) {
            Entry::Occupied(mut by_lang) => by_lang.get_mut().push(fc),
            Entry::Vacant(by_lang) => {
                by_lang.insert(vec![fc]);
            }
        };
    }

    let linesep = str_repeat("-", 81);

    if by_file {
        // TODO(cgag): Need sorting for by_file as well.
        println!("{}", linesep);
        println!(" {0: <18} {1: >8} {2: >12} {3: >12} {4: >12} {5: >12}",
                 "Language",
                 "Files",
                 "Lines",
                 "Blank",
                 "Comment",
                 "Code");
        println!("{}", linesep);

        for (lang, filecounts) in by_lang {
            let mut total = Count::default();
            for fc in &filecounts {
                total.merge(&fc.count);
            }

            println!("{}", linesep);
            println!(" {0: <18} {1: >8} {2: >12} {3: >12} {4: >12} {5: >12}",
                     lang,
                     filecounts.len(),
                     total.lines,
                     total.blank,
                     total.comment,
                     total.code);

            println!("{}", linesep);
            for fc in filecounts {
                println!("|{0: <25} {1: >12} {2: >12} {3: >12} {4: >12}",
                         last_n_chars(&fc.path, 25),
                         fc.count.lines,
                         fc.count.blank,
                         fc.count.comment,
                         fc.count.code);
            }
        }
    } else {
        let mut lang_totals: HashMap<&Lang, LangTotal> = HashMap::new();
        for (lang, filecounts) in &by_lang {
            let mut lang_total = Count::default();
            for fc in filecounts {
                lang_total.merge(&fc.count);
            }
            lang_totals.insert(lang,
                               LangTotal {
                                   files: filecounts.len() as u32,
                                   count: lang_total,
                               });
        }

        let mut totals_by_lang = lang_totals.iter().collect::<Vec<(&&Lang, &LangTotal)>>();
        match sort {
            "language" => totals_by_lang.sort_by(|&(l1, _), &(l2, _)| l1.to_s().cmp(l2.to_s())),
            "files" => totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.files.cmp(&c1.files)),
            "code" => {
                totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.count.code.cmp(&c1.count.code))
            }
            "comment" => {
                totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.count.comment.cmp(&c1.count.comment))
            }
            "blank" => {
                totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.count.blank.cmp(&c1.count.blank))
            }
            "lines" => {
                totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.count.lines.cmp(&c1.count.lines))
            }
            _ => {
                println!("invalid sort option {}, sorting by code", sort);
                totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.count.code.cmp(&c1.count.code))
            }
        }

        println!("{}", linesep);
        println!(" {0: <18} {1: >8} {2: >12} {3: >12} {4: >12} {5: >12}",
                 "Language",
                 "Files",
                 "Lines",
                 "Blank",
                 "Comment",
                 "Code");
        println!("{}", linesep);

        for &(lang, total) in &totals_by_lang {
            println!(" {0: <18} {1: >8} {2: >12} {3: >12} {4: >12} {5: >12}",
                     lang,
                     total.files,
                     total.count.lines,
                     total.count.blank,
                     total.count.comment,
                     total.count.code);
        }

        let mut totals = LangTotal {
            files: 0,
            count: Count::default(),
        };
        for &(_, total) in &totals_by_lang {
            totals.files += total.files;
            totals.count.code += total.count.code;
            totals.count.blank += total.count.blank;
            totals.count.comment += total.count.comment;
            totals.count.lines += total.count.lines;
        }

        println!("{}", linesep);
        println!(" {0: <18} {1: >8} {2: >12} {3: >12} {4: >12} {5: >12}",
                 "Total",
                 totals.files,
                 totals.count.lines,
                 totals.count.blank,
                 totals.count.comment,
                 totals.count.code);
        println!("{}", linesep);
    }
}

fn last_n_chars(s: &str, n: usize) -> String {
    if s.len() <= n {
        return String::from(s);
    }
    s.chars().skip(s.len() - n).collect::<String>()
}

fn str_repeat(s: &str, n: usize) -> String {
    std::iter::repeat(s).take(n).collect::<Vec<_>>().join("")
}
<|endoftext|>


<commit_before>extern crate regex;
extern crate irc;

mod starred_message;

use irc::client::prelude::*;
use std::default::Default;
use starred_message::StarredMessage;

fn main() {
	let server = IrcServer::from_config(Config{
		owners   : Some(vec!["nabijaczleweli".to_string()]),
		nickname : Some("NabBot".to_string()),
		username : Some("NabBot".to_string()),
		realname : Some("наб's IRC bot".to_string()),
		server   : Some("chat.freenode.net".to_string()),
		use_ssl  : Some(true),
		channels : Some(vec!["#loungecpp".to_string()]),
		user_info: Some("наб's IRC bot".to_string()),
		..Default::default()
	}).unwrap();
	server.identify().unwrap();

	let mut starred: Vec<StarredMessage> = Vec::new();

	for message in server.iter() {
		if let Ok(message) = message {
			let sender = message.get_source_nickname().map(String::from);

			match Command::from_message_io(Ok(message)) {
				Ok(Command::JOIN(_, _, _))        => println!("I'm in"),
				Ok(Command::PRIVMSG(target, msg)) => {
					let to_self = target == "NabBot";

					if (to_self && msg == "Navaer") || msg == "Navaer, NabBot" {
						server.send_quit("Mára mesta").unwrap();
					} else if to_self && msg.starts_with("add ") {
						if let Some(star_message) = StarredMessage::from_message_content(&msg[4..], sender) {
							if match starred.iter_mut().find(|fmsg| (&fmsg.sender, &fmsg.message) == (&star_message.sender, &star_message.message)) {
								Some(ref mut existing_message) => {
									existing_message.stars += 1;
									existing_message.starrers.extend(star_message.starrers.clone());
									false
								},
								None => true,
							} {
								starred.push(star_message);  // Can't do it in match arm because it'd borrow starred as &mut twice
							}
						}
					} else if to_self && msg == "help" && sender.is_some() {
						let sender = &*&sender.unwrap();

						server.send_notice(sender, r#"Cummands:"#).unwrap();
						server.send_notice(sender, r#"  "add <" username ">" message content -- add your star to a message"#).unwrap();
						server.send_notice(sender, r#"  "help" -- send this help notice to sender"#).unwrap();
						server.send_notice(sender, r#"  "dump" -- dump all star data to sender"#).unwrap();
						server.send_notice(sender, r#"Execute via sending a PRIVMSG (as by "/msg") to NabBot"#).unwrap();
					} else if to_self && msg == "dump" && sender.is_some() {
						server.send_privmsg(&*&sender.unwrap(), &*&format!("{:?}", starred)).unwrap();
					}
				}
				_ => (),
			}
		}
	}
}
<commit_msg>Add "delete" command<commit_after>extern crate regex;
extern crate irc;

mod starred_message;

use irc::client::prelude::*;
use std::default::Default;
use starred_message::StarredMessage;

fn main() {
	let server = IrcServer::from_config(Config{
		owners   : Some(vec!["nabijaczleweli".to_string()]),
		nickname : Some("NabBot".to_string()),
		username : Some("NabBot".to_string()),
		realname : Some("наб's IRC bot".to_string()),
		server   : Some("chat.freenode.net".to_string()),
		use_ssl  : Some(true),
		channels : Some(vec!["#loungecpp".to_string()]),
		user_info: Some("наб's IRC bot".to_string()),
		..Default::default()
	}).unwrap();
	server.identify().unwrap();

	let mut starred: Vec<StarredMessage> = Vec::new();

	for message in server.iter() {
		if let Ok(message) = message {
			let sender = message.get_source_nickname().map(String::from);

			match Command::from_message_io(Ok(message)) {
				Ok(Command::JOIN(_, _, _))        => println!("I'm in"),
				Ok(Command::PRIVMSG(target, msg)) => {
					let to_self = target == "NabBot";

					if (to_self && msg == "Navaer") || msg == "Navaer, NabBot" {
						server.send_quit("Mára mesta").unwrap();
					} else if to_self && msg.starts_with("add ") {
						if let Some(star_message) = StarredMessage::from_message_content(&msg[4..], sender) {
							if match starred.iter_mut().find(|fmsg| (&fmsg.sender, &fmsg.message) == (&star_message.sender, &star_message.message)) {
								Some(ref mut existing_message) => {
									existing_message.stars += 1;
									existing_message.starrers.extend(star_message.starrers.clone());
									false
								},
								None => true,
							} {
								starred.push(star_message);  // Can't do it in match arm because it'd borrow starred as &mut twice
							}
						}
					} else if to_self && msg.starts_with("remove ") {
						if let Some(star_message) = StarredMessage::from_message_content(&msg[7..], sender) {
							if let Some(index) = match starred.iter_mut().enumerate().find(
								|fmsg| (&fmsg.1.sender, &fmsg.1.message) == (&star_message.sender, &star_message.message)
							) {
								Some((idx, ref mut existing_message)) =>
									if let Some(starrer_pos) = existing_message.starrers.iter().position(|starrer| starrer == &existing_message.starrers[0]) {
										existing_message.stars -= 1;
										existing_message.starrers.swap_remove(starrer_pos);
										Some(idx)
									} else {
										None
									},
								None => None,
							} {
								starred.swap_remove(index);  // Can't do it in match arm because it'd borrow starred as &mut twice
							}
						}
					} else if to_self && msg == "help" && sender.is_some() {
						let sender = &*&sender.unwrap();

						server.send_notice(sender, r#"Cummands:"#).unwrap();
						server.send_notice(sender, r#"  "add <" username ">" message content -- add your star to a message"#).unwrap();
						server.send_notice(sender, r#"  "help" -- send this help notice to sender"#).unwrap();
						server.send_notice(sender, r#"  "dump" -- dump all star data to sender"#).unwrap();
						server.send_notice(sender, r#"Execute via sending a PRIVMSG (as by "/msg") to NabBot"#).unwrap();
					} else if to_self && msg == "dump" && sender.is_some() {
						server.send_privmsg(&*&sender.unwrap(), &*&format!("{:?}", starred)).unwrap();
					}
				}
				_ => (),
			}
		}
	}
}
<|endoftext|>


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

#![doc(html_logo_url = "https://raw.githubusercontent.com/Luz/hexdino/master/logo.png")]
#![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 infoline = 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 infoline,
        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 => {
                    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 => {
                    // The 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);
                        // infoline.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 infoline,
            cursor,
            screenoffset,
        );
    }

    refresh();
    endwin();
}
<commit_msg>Fix underflow when '$' without data<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 infoline = 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 infoline,
        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 => {
                    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
                        if cursor.pos >= 1 { // Ensure no underflow
                            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 => {
                    // The 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);
                        // infoline.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 infoline,
            cursor,
            screenoffset,
        );
    }

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