<commit_before>extern crate clap;
extern crate fish;

use clap::{App, Arg};
use std::process;

const NAME: &str = env!("CARGO_PKG_NAME");
const VERSION: &str = env!("CARGO_PKG_VERSION");

fn main() {
    let matches = App::new(NAME)
        .version(VERSION)
        .author("Marc Noirot <marc.noirot@gmail.com>")
        .about("Fish language interpreter")
        .arg(
            Arg::with_name("INPUT")
                .help("set the input file to use")
                .conflicts_with("code")
                .value_name("FILE")
                .index(1),
        )
        .arg(
            Arg::with_name("code")
                .short("c")
                .long("code")
                .help("string of instructions to execute instead of FILE")
                .takes_value(true)
                .value_name("CODE"),
        )
        .arg(
            Arg::with_name("string")
                .short("s")
                .long("string")
                .help("push strings onto the stack before execution starts")
                .takes_value(true)
                .value_name("STRING")
                .multiple(true)
                .number_of_values(1),
        )
        .arg(
            Arg::with_name("value")
                .short("v")
                .long("value")
                .help("push numbers onto the stack before execution starts")
                .takes_value(true)
                .value_name("NUMBER")
                .multiple(true)
                .number_of_values(1),
        )
        .arg(
            Arg::with_name("tick")
                .short("t")
                .long("tick")
                .help("define a delay between the execution of each instruction")
                .takes_value(true)
                .value_name("DELAY"),
        )
        .arg(
            Arg::with_name("always_tick")
                .short("a")
                .long("always-tick")
                .help(
                    "make every instruction cause a tick, even whitespace and skipped instructions",
                ),
        )
        .arg(
            Arg::with_name("debug")
                .short("d")
                .long("debug")
                .help("dump interpreter state before executing an instruction"),
        )
        .get_matches();

    let code_box = match matches.value_of("code") {
        Some(c) => fish::CodeBox::load_from_string(&c),
        None => {
            let input = match matches.value_of("INPUT") {
                Some(v) => v,
                None => {
                    println!("{}", matches.usage());
                    process::exit(1)
                }
            };

            match fish::CodeBox::load_from_file(&input) {
                Ok(cb) => cb,
                Err(e) => {
                    println!("Error: {}", e);
                    process::exit(2)
                }
            }
        }
    };

    let input = std::io::stdin();
    let output = std::io::stdout();

    let mut fish = fish::Interpreter::new(input, output);

    if let Some(strings) = matches.values_of("string") {
        for c in strings {
            fish.push_str(c);
        }
    }

    if let Some(numbers) = matches.values_of("value") {
        for c in numbers {
            let n = match c.parse::<i64>() {
                Ok(v) => v,
                Err(e) => {
                    println!("Error: {}", e);
                    process::exit(2)
                }
            };
            fish.push_i64(n);
        }
    }

    if matches.is_present("debug") {
        fish.trace = true;
    }

    if fish.run(&code_box).is_err() {
        println!("something smells fishy...");
        process::exit(3);
    }

    println!();
}
<commit_msg>Simplify code<commit_after>extern crate clap;
extern crate fish;

use clap::{App, Arg};
use std::process;

const NAME: &str = env!("CARGO_PKG_NAME");
const VERSION: &str = env!("CARGO_PKG_VERSION");

fn main() {
    let matches = App::new(NAME)
        .version(VERSION)
        .author("Marc Noirot <marc.noirot@gmail.com>")
        .about("Fish language interpreter")
        .arg(
            Arg::with_name("INPUT")
                .help("set the input file to use")
                .conflicts_with("code")
                .value_name("FILE")
                .index(1),
        )
        .arg(
            Arg::with_name("code")
                .short("c")
                .long("code")
                .help("string of instructions to execute instead of FILE")
                .takes_value(true)
                .value_name("CODE"),
        )
        .arg(
            Arg::with_name("string")
                .short("s")
                .long("string")
                .help("push strings onto the stack before execution starts")
                .takes_value(true)
                .value_name("STRING")
                .multiple(true)
                .number_of_values(1),
        )
        .arg(
            Arg::with_name("value")
                .short("v")
                .long("value")
                .help("push numbers onto the stack before execution starts")
                .takes_value(true)
                .value_name("NUMBER")
                .multiple(true)
                .number_of_values(1),
        )
        .arg(
            Arg::with_name("tick")
                .short("t")
                .long("tick")
                .help("define a delay between the execution of each instruction")
                .takes_value(true)
                .value_name("DELAY"),
        )
        .arg(
            Arg::with_name("always_tick")
                .short("a")
                .long("always-tick")
                .help(
                    "make every instruction cause a tick, even whitespace and skipped instructions",
                ),
        )
        .arg(
            Arg::with_name("debug")
                .short("d")
                .long("debug")
                .help("dump interpreter state before executing an instruction"),
        )
        .get_matches();

    let code_box = match matches.value_of("code") {
        Some(c) => fish::CodeBox::load_from_string(&c),
        None => {
            let input = match matches.value_of("INPUT") {
                Some(v) => v,
                None => {
                    println!("{}", matches.usage());
                    process::exit(1)
                }
            };

            match fish::CodeBox::load_from_file(&input) {
                Ok(cb) => cb,
                Err(e) => {
                    println!("Error: {}", e);
                    process::exit(2)
                }
            }
        }
    };

    let input = std::io::stdin();
    let output = std::io::stdout();

    let mut fish = fish::Interpreter::new(input, output);

    if let Some(strings) = matches.values_of("string") {
        for c in strings {
            fish.push_str(c);
        }
    }

    if let Some(numbers) = matches.values_of("value") {
        for c in numbers {
            let n = match c.parse::<i64>() {
                Ok(v) => v,
                Err(e) => {
                    println!("Error: {}", e);
                    process::exit(2)
                }
            };
            fish.push_i64(n);
        }
    }

    fish.trace = matches.is_present("debug");

    if fish.run(&code_box).is_err() {
        println!("something smells fishy...");
        process::exit(3);
    }

    println!();
}
<|endoftext|>


<commit_before>// Worklog is for recording your hours.
//

extern crate chrono;
extern crate csv;
extern crate getopts;
extern crate rustc_serialize;

mod error;
mod timeclock;
mod util;

use chrono::*;
use error::WorklogError;
use getopts::Options;
use std::env;
use std::error::Error;
use std::fs::File;
use std::fs::OpenOptions;
use std::path::PathBuf;
use timeclock::direction::Direction;

#[cfg(target_family = "unix")]
static CSV_FILE_NAME: &'static str = ".worklog.csv";
#[cfg(target_family = "windows")]
static CSV_FILE_NAME: &'static str = "worklog.csv";

// TODO This belongs in a config file somewhere
const WEEKSTART: i64 = Weekday::Sat as i64;

fn print_full_summary(file: &File) -> Result<u8, WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    let entries = timeclock::pair_time_entries(csv_entries);
    let records = timeclock::collect_date_records(entries);

    let mut total_hours: f64 = 0.0;
    for rec in records {
        total_hours += rec.hours();
        println!("{}", rec);
    }

    println!("Total Hours: {:.2}", total_hours);
    Ok(0)
}

fn print_short_summary(file: &File,
                       since: Date<FixedOffset>)
                       -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    let entries = timeclock::pair_time_entries(csv_entries);
    let records = timeclock::collect_date_records(entries);

    let mut total_hours: f64 = 0.0;
    for rec in records {
        if rec.date >= since {
            total_hours += rec.hours();
            println!("{}", rec);
        }
    }

    println!("Total Hours: {:.2}", total_hours);
    Ok(())
}

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

/// Get the path for the csv data file
fn get_csv_path() -> Result<PathBuf, WorklogError> {
    let mut data_path = match env::home_dir() {
        Some(path) => path,
        None => {
            return Err(env::VarError::NotPresent).map_err(WorklogError::Env)
        }
    };

    data_path.push(CSV_FILE_NAME);
    return Ok(data_path);
}

fn main0() -> Result<i8, WorklogError> {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();

    let mut opts = Options::new();
    opts.optflagopt("i", "in", "in time", "TIME");
    opts.optflagopt("o", "out", "out time", "TIME");
    opts.optopt("m", "memo", "the memo", "MEMO");
    opts.optflag("s", "summary", "show the full summary");
    opts.optflag("h", "help", "print this help menu");

    let matches = try!(opts.parse(&args[1..]));

    let csv_path = try!(get_csv_path());

    let csv_file = try!(OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(csv_path));

    if matches.opt_present("h") {
        print_usage(&program, opts);

    } else if matches.opts_present(&["i".to_owned(), "o".to_owned()]) {
        // The ridiculous array above isn't my fault.

        // Reject --in and --out if present at the same time
        if matches.opt_present("i") && matches.opt_present("o") {
            println!("--in or --out, not both");
            return Ok(1);
        }

        let dir = match matches.opt_present("i") {
            true => Direction::In,
            false => Direction::Out,
        };

        let time =
            matches.opt_str("i").or(matches.opt_default("o", "now")).unwrap();
        let time = try!(util::parse_multi_time_fmt(&time));

        let memo = matches.opt_str("m").or(Some("".to_owned())).unwrap();
        timeclock::mark_time(dir, time, memo, csv_file);

        let dir_str = format!("{}", dir).to_lowercase();
        println!("Clocked {} at {}", dir_str, time.format("%F %I:%M %P"));

    } else if matches.opt_present("s") {
        try!(print_full_summary(&csv_file));

    } else {
        let days_back = (-WEEKSTART + 7) % 7;
        let start_date = util::now().date() - Duration::days(days_back);
        try!(print_short_summary(&csv_file, start_date));
    }

    Ok(0)
}

fn main() {
    match main0() {
        Ok(_) => {}
        Err(err) => println!("Whoops! {}", err.description()),
    };
}
<commit_msg>handle --in or --out being None<commit_after>// Worklog is for recording your hours.
//

extern crate chrono;
extern crate csv;
extern crate getopts;
extern crate rustc_serialize;

mod error;
mod timeclock;
mod util;

use chrono::*;
use error::WorklogError;
use getopts::Options;
use std::env;
use std::error::Error;
use std::fs::File;
use std::fs::OpenOptions;
use std::path::PathBuf;
use timeclock::direction::Direction;

#[cfg(target_family = "unix")]
static CSV_FILE_NAME: &'static str = ".worklog.csv";
#[cfg(target_family = "windows")]
static CSV_FILE_NAME: &'static str = "worklog.csv";

// TODO This belongs in a config file somewhere
const WEEKSTART: i64 = Weekday::Sat as i64;

fn print_full_summary(file: &File) -> Result<u8, WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    let entries = timeclock::pair_time_entries(csv_entries);
    let records = timeclock::collect_date_records(entries);

    let mut total_hours: f64 = 0.0;
    for rec in records {
        total_hours += rec.hours();
        println!("{}", rec);
    }

    println!("Total Hours: {:.2}", total_hours);
    Ok(0)
}

fn print_short_summary(file: &File,
                       since: Date<FixedOffset>)
                       -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    let entries = timeclock::pair_time_entries(csv_entries);
    let records = timeclock::collect_date_records(entries);

    let mut total_hours: f64 = 0.0;
    for rec in records {
        if rec.date >= since {
            total_hours += rec.hours();
            println!("{}", rec);
        }
    }

    println!("Total Hours: {:.2}", total_hours);
    Ok(())
}

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

/// Get the path for the csv data file
fn get_csv_path() -> Result<PathBuf, WorklogError> {
    let mut data_path = match env::home_dir() {
        Some(path) => path,
        None => {
            return Err(env::VarError::NotPresent).map_err(WorklogError::Env)
        }
    };

    data_path.push(CSV_FILE_NAME);
    return Ok(data_path);
}

fn main0() -> Result<i8, WorklogError> {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();

    let mut opts = Options::new();
    opts.optflagopt("i", "in", "in time", "TIME");
    opts.optflagopt("o", "out", "out time", "TIME");
    opts.optopt("m", "memo", "the memo", "MEMO");
    opts.optflag("s", "summary", "show the full summary");
    opts.optflag("h", "help", "print this help menu");

    let matches = try!(opts.parse(&args[1..]));

    let csv_path = try!(get_csv_path());

    let csv_file = try!(OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(csv_path));

    if matches.opt_present("h") {
        print_usage(&program, opts);

    } else if matches.opts_present(&["i".to_owned(), "o".to_owned()]) {
        // The ridiculous array above isn't my fault.

        // Reject --in and --out if present at the same time
        if matches.opt_present("i") && matches.opt_present("o") {
            println!("--in or --out, not both");
            return Ok(1);
        }

        let dir = match matches.opt_present("i") {
            true => Direction::In,
            false => Direction::Out,
        };

        let time = matches.opt_str("i")
            .or(matches.opt_str("o"))
            .or(Some("now".to_owned()))
            .unwrap();
        let time = try!(util::parse_multi_time_fmt(&time));

        let memo = matches.opt_str("m").or(Some("".to_owned())).unwrap();
        timeclock::mark_time(dir, time, memo, csv_file);

        let dir_str = format!("{}", dir).to_lowercase();
        println!("Clocked {} at {}", dir_str, time.format("%F %I:%M %P"));

    } else if matches.opt_present("s") {
        try!(print_full_summary(&csv_file));

    } else {
        let days_back = (-WEEKSTART + 7) % 7;
        let start_date = util::now().date() - Duration::days(days_back);
        try!(print_short_summary(&csv_file, start_date));
    }

    Ok(0)
}

fn main() {
    match main0() {
        Ok(_) => {}
        Err(err) => println!("Whoops! {}", err.description()),
    };
}
<|endoftext|>


<commit_before>use anyhow::Result;
use crossterm::event::{KeyEvent, KeyModifiers};
use crossterm::{
    event::{self, DisableMouseCapture, EnableMouseCapture, Event as CEvent, KeyCode},
    execute,
    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use histogram::Histogram;
use pinger::{ping, PingResult};
use std::io;
use std::io::Write;
use std::ops::Add;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use structopt::StructOpt;
use tui::backend::CrosstermBackend;
use tui::layout::{Constraint, Direction, Layout};
use tui::style::{Color, Modifier, Style};
use tui::text::Span;
use tui::widgets::{Axis, Block, Borders, Chart, Dataset, GraphType, Paragraph};
use tui::{symbols, Terminal};

#[derive(Debug, StructOpt)]
#[structopt(name = "gping", about = "Ping, but with a graph.")]
struct Args {
    #[structopt(help = "Host or IP to ping")]
    host: String,
}

struct App {
    data: Vec<(f64, f64)>,
    capacity: usize,
    idx: i64,
    window: [f64; 2],
}

impl App {
    fn new(capacity: usize) -> Self {
        App {
            data: Vec::with_capacity(capacity),
            capacity,
            idx: 0,
            window: [0.0, capacity as f64],
        }
    }
    fn update(&mut self, item: Option<Duration>) {
        self.idx += 1;
        if self.data.len() >= self.capacity {
            self.data.remove(0);
            self.window[0] += 1_f64;
            self.window[1] += 1_f64;
        }
        match item {
            Some(dur) => self.data.push((self.idx as f64, dur.as_micros() as f64)),
            None => self.data.push((self.idx as f64, 0_f64)),
        }
    }
    fn stats(&self) -> Histogram {
        let mut hist = Histogram::new();

        for (_, val) in self.data.iter().filter(|v| v.1 != 0f64) {
            hist.increment(*val as u64).unwrap_or(());
        }

        hist
    }
    fn y_axis_bounds(&self) -> [f64; 2] {
        let min = self
            .data
            .iter()
            .map(|v| v.1)
            .fold(f64::INFINITY, |a, b| a.min(b));
        let max = self.data.iter().map(|v| v.1).fold(0f64, |a, b| a.max(b));
        // Add a 10% buffer to the top and bottom
        let max_10_percent = (max * 10_f64) / 100_f64;
        let min_10_percent = (min * 10_f64) / 100_f64;
        [min - min_10_percent, max + max_10_percent]
    }
    fn y_axis_labels(&self, bounds: [f64; 2]) -> Vec<Span> {
        // Split into 5 sections
        let min = bounds[0];
        let max = bounds[1];

        let difference = max - min;
        let increment = Duration::from_micros((difference / 3f64) as u64);
        let duration = Duration::from_micros(min as u64);

        vec![
            Span::styled(
                format!("{:?}", duration),
                Style::default().add_modifier(Modifier::BOLD),
            ),
            Span::raw(format!("{:?}", duration.add(increment))),
            Span::raw(format!("{:?}", duration.add(increment) * 2)),
            Span::raw(format!("{:?}", duration.add(increment) * 3)),
            Span::styled(
                format!("{:?}", duration.add(increment) * 4),
                Style::default().add_modifier(Modifier::BOLD),
            ),
        ]
    }
}

#[derive(Debug)]
enum Event {
    Update(PingResult),
    Input(KeyEvent),
}

fn main() -> Result<()> {
    let args = Args::from_args();

    let mut app = App::new(100);
    enable_raw_mode()?;
    let mut stdout = io::stdout();
    execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
    let backend = CrosstermBackend::new(stdout);

    let mut terminal = Terminal::new(backend)?;

    terminal.clear()?;

    let (key_tx, rx) = mpsc::channel();

    let ping_tx = key_tx.clone();

    let host = args.host.clone();

    // Pump ping messages into the queue
    thread::spawn(move || -> Result<()> {
        let stream = ping(host).expect("Error pinging");

        loop {
            ping_tx.send(Event::Update(stream.recv()?))?;
        }
    });

    // Pump keyboard messages into the queue
    thread::spawn(move || -> Result<()> {
        loop {
            if event::poll(Duration::from_secs(1)).unwrap() {
                if let CEvent::Key(key) = event::read().unwrap() {
                    key_tx.send(Event::Input(key)).unwrap();
                }
            }
        }
    });

    loop {
        match rx.recv()? {
            Event::Update(ping_result) => {
                match ping_result {
                    PingResult::Pong(duration) => app.update(Some(duration)),
                    PingResult::Timeout => app.update(None),
                };
                terminal.draw(|f| {
                    let chunks = Layout::default()
                        .direction(Direction::Vertical)
                        .margin(2)
                        .constraints(
                            [Constraint::Percentage(5), Constraint::Percentage(10)].as_ref(),
                        )
                        .split(f.size());

                    let header_layout = Layout::default()
                        .direction(Direction::Horizontal)
                        .constraints(
                            [
                                Constraint::Percentage(25),
                                Constraint::Percentage(25),
                                Constraint::Percentage(25),
                                Constraint::Percentage(25),
                            ]
                            .as_ref(),
                        )
                        .split(chunks[0]);

                    f.render_widget(
                        Paragraph::new(format!("Pinging {}", &args.host)),
                        header_layout[0],
                    );

                    let stats = app.stats();

                    f.render_widget(
                        Paragraph::new(format!(
                            "min {:?}",
                            Duration::from_micros(stats.minimum().unwrap_or(0))
                        )),
                        header_layout[1],
                    );
                    f.render_widget(
                        Paragraph::new(format!(
                            "max {:?}",
                            Duration::from_micros(stats.maximum().unwrap_or(0))
                        )),
                        header_layout[2],
                    );
                    f.render_widget(
                        Paragraph::new(format!(
                            "p95 {:?}",
                            Duration::from_micros(stats.percentile(95.0).unwrap_or(0))
                        )),
                        header_layout[3],
                    );

                    let dataset = Dataset::default()
                        .marker(symbols::Marker::Braille)
                        .style(Style::default().fg(Color::Cyan))
                        .graph_type(GraphType::Line)
                        .data(&app.data);

                    let y_axis_bounds = app.y_axis_bounds();

                    let chart = Chart::new(vec![dataset])
                        .block(Block::default().borders(Borders::NONE))
                        .x_axis(
                            Axis::default()
                                .style(Style::default().fg(Color::Gray))
                                .bounds(app.window),
                        )
                        .y_axis(
                            Axis::default()
                                .title("Time")
                                .style(Style::default().fg(Color::Gray))
                                .bounds(y_axis_bounds)
                                .labels(app.y_axis_labels(y_axis_bounds)),
                        );
                    f.render_widget(chart, chunks[1]);
                })?;
            }
            Event::Input(input) => match input.code {
                KeyCode::Char('q') | KeyCode::Esc => {
                    break;
                }
                KeyCode::Char('c') if input.modifiers == KeyModifiers::CONTROL => {
                    break;
                }
                _ => {}
            },
        }
    }

    disable_raw_mode()?;
    execute!(
        terminal.backend_mut(),
        LeaveAlternateScreen,
        DisableMouseCapture
    )?;
    terminal.show_cursor()?;

    Ok(())
}
<commit_msg>Allow users to specify custom buffer sizes.<commit_after>use anyhow::Result;
use crossterm::event::{KeyEvent, KeyModifiers};
use crossterm::{
    event::{self, DisableMouseCapture, EnableMouseCapture, Event as CEvent, KeyCode},
    execute,
    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use histogram::Histogram;
use pinger::{ping, PingResult};
use std::io;
use std::io::Write;
use std::ops::Add;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use structopt::StructOpt;
use tui::backend::CrosstermBackend;
use tui::layout::{Constraint, Direction, Layout};
use tui::style::{Color, Modifier, Style};
use tui::text::Span;
use tui::widgets::{Axis, Block, Borders, Chart, Dataset, GraphType, Paragraph};
use tui::{symbols, Terminal};

#[derive(Debug, StructOpt)]
#[structopt(name = "gping", about = "Ping, but with a graph.")]
struct Args {
    #[structopt(help = "Host or IP to ping")]
    host: String,
    #[structopt(
        short,
        long,
        help = "Determines the number pings to display. Defaults to 100"
    )]
    buffer: Option<usize>,
}

struct App {
    data: Vec<(f64, f64)>,
    capacity: usize,
    idx: i64,
    window: [f64; 2],
}

impl App {
    fn new(capacity: usize) -> Self {
        App {
            data: Vec::with_capacity(capacity),
            capacity,
            idx: 0,
            window: [0.0, capacity as f64],
        }
    }
    fn update(&mut self, item: Option<Duration>) {
        self.idx += 1;
        if self.data.len() >= self.capacity {
            self.data.remove(0);
            self.window[0] += 1_f64;
            self.window[1] += 1_f64;
        }
        match item {
            Some(dur) => self.data.push((self.idx as f64, dur.as_micros() as f64)),
            None => self.data.push((self.idx as f64, 0_f64)),
        }
    }
    fn stats(&self) -> Histogram {
        let mut hist = Histogram::new();

        for (_, val) in self.data.iter().filter(|v| v.1 != 0f64) {
            hist.increment(*val as u64).unwrap_or(());
        }

        hist
    }
    fn y_axis_bounds(&self) -> [f64; 2] {
        let min = self
            .data
            .iter()
            .map(|v| v.1)
            .fold(f64::INFINITY, |a, b| a.min(b));
        let max = self.data.iter().map(|v| v.1).fold(0f64, |a, b| a.max(b));
        // Add a 10% buffer to the top and bottom
        let max_10_percent = (max * 10_f64) / 100_f64;
        let min_10_percent = (min * 10_f64) / 100_f64;
        [min - min_10_percent, max + max_10_percent]
    }
    fn y_axis_labels(&self, bounds: [f64; 2]) -> Vec<Span> {
        // Split into 5 sections
        let min = bounds[0];
        let max = bounds[1];

        let difference = max - min;
        let increment = Duration::from_micros((difference / 3f64) as u64);
        let duration = Duration::from_micros(min as u64);

        vec![
            Span::styled(
                format!("{:?}", duration),
                Style::default().add_modifier(Modifier::BOLD),
            ),
            Span::raw(format!("{:?}", duration.add(increment))),
            Span::raw(format!("{:?}", duration.add(increment) * 2)),
            Span::raw(format!("{:?}", duration.add(increment) * 3)),
            Span::styled(
                format!("{:?}", duration.add(increment) * 4),
                Style::default().add_modifier(Modifier::BOLD),
            ),
        ]
    }
}

#[derive(Debug)]
enum Event {
    Update(PingResult),
    Input(KeyEvent),
}

fn main() -> Result<()> {
    let args = Args::from_args();
    let mut app = App::new(args.buffer.unwrap_or(100));
    enable_raw_mode()?;
    let mut stdout = io::stdout();
    execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
    let backend = CrosstermBackend::new(stdout);

    let mut terminal = Terminal::new(backend)?;

    terminal.clear()?;

    let (key_tx, rx) = mpsc::channel();

    let ping_tx = key_tx.clone();

    let host = args.host.clone();

    // Pump ping messages into the queue
    thread::spawn(move || -> Result<()> {
        let stream = ping(host).expect("Error pinging");

        loop {
            ping_tx.send(Event::Update(stream.recv()?))?;
        }
    });

    // Pump keyboard messages into the queue
    thread::spawn(move || -> Result<()> {
        loop {
            if event::poll(Duration::from_secs(1)).unwrap() {
                if let CEvent::Key(key) = event::read().unwrap() {
                    key_tx.send(Event::Input(key)).unwrap();
                }
            }
        }
    });

    loop {
        match rx.recv()? {
            Event::Update(ping_result) => {
                match ping_result {
                    PingResult::Pong(duration) => app.update(Some(duration)),
                    PingResult::Timeout => app.update(None),
                };
                terminal.draw(|f| {
                    let chunks = Layout::default()
                        .direction(Direction::Vertical)
                        .margin(2)
                        .constraints(
                            [Constraint::Percentage(5), Constraint::Percentage(10)].as_ref(),
                        )
                        .split(f.size());

                    let header_layout = Layout::default()
                        .direction(Direction::Horizontal)
                        .constraints(
                            [
                                Constraint::Percentage(25),
                                Constraint::Percentage(25),
                                Constraint::Percentage(25),
                                Constraint::Percentage(25),
                            ]
                            .as_ref(),
                        )
                        .split(chunks[0]);

                    f.render_widget(
                        Paragraph::new(format!("Pinging {}", &args.host)),
                        header_layout[0],
                    );

                    let stats = app.stats();

                    f.render_widget(
                        Paragraph::new(format!(
                            "min {:?}",
                            Duration::from_micros(stats.minimum().unwrap_or(0))
                        )),
                        header_layout[1],
                    );
                    f.render_widget(
                        Paragraph::new(format!(
                            "max {:?}",
                            Duration::from_micros(stats.maximum().unwrap_or(0))
                        )),
                        header_layout[2],
                    );
                    f.render_widget(
                        Paragraph::new(format!(
                            "p95 {:?}",
                            Duration::from_micros(stats.percentile(95.0).unwrap_or(0))
                        )),
                        header_layout[3],
                    );

                    let dataset = Dataset::default()
                        .marker(symbols::Marker::Braille)
                        .style(Style::default().fg(Color::Cyan))
                        .graph_type(GraphType::Line)
                        .data(&app.data);

                    let y_axis_bounds = app.y_axis_bounds();

                    let chart = Chart::new(vec![dataset])
                        .block(Block::default().borders(Borders::NONE))
                        .x_axis(
                            Axis::default()
                                .style(Style::default().fg(Color::Gray))
                                .bounds(app.window),
                        )
                        .y_axis(
                            Axis::default()
                                .title("Time")
                                .style(Style::default().fg(Color::Gray))
                                .bounds(y_axis_bounds)
                                .labels(app.y_axis_labels(y_axis_bounds)),
                        );
                    f.render_widget(chart, chunks[1]);
                })?;
            }
            Event::Input(input) => match input.code {
                KeyCode::Char('q') | KeyCode::Esc => {
                    break;
                }
                KeyCode::Char('c') if input.modifiers == KeyModifiers::CONTROL => {
                    break;
                }
                _ => {}
            },
        }
    }

    disable_raw_mode()?;
    execute!(
        terminal.backend_mut(),
        LeaveAlternateScreen,
        DisableMouseCapture
    )?;
    terminal.show_cursor()?;

    Ok(())
}
<|endoftext|>


<commit_before>#[macro_use] extern crate error_chain;
#[macro_use] extern crate prettytable;
#[macro_use] extern crate stderr;
extern crate chan;
extern crate clap;
extern crate filetime;
extern crate globset;
extern crate pretty_bytes;
extern crate separator;

#[macro_use] mod errors;
mod compression;
mod lists;
mod structs;

use clap::{App, Arg};
use errors::*;
use globset::{GlobBuilder, GlobSet, GlobSetBuilder};
use lists::*;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::mpsc;
use structs::*;

quick_main!(run);

fn run() -> Result<()> {
    let matches = App::new("static-compress")
        .version("0.1")
        .about("Create statically-compresed copies of matching files")
        .author("NeoSmart Technologies")
        .arg(Arg::with_name("compressor")
            .short("c")
            .long("compressor")
            .value_name("[brotli|gzip|zopfli]")
            .help("The compressor to use (default: gzip)")
            .takes_value(true))
        .arg(Arg::with_name("threads")
            .short("j")
            .long("threads")
            .value_name("COUNT")
            .help("The number of simultaneous compressions (default: 1)")
            .takes_value(true))
        .arg(Arg::with_name("filters")
            .value_name("FILTER")
            .multiple(true)
            .required(true))
        .arg(Arg::with_name("ext")
            .short("e")
            .value_name("EXT")
            .long("extension")
            .help("The extension to use for compressed files (default: gz or br)"))
        /*.arg(Arg::with_name("excludes")
            .short("x")
            .value_name("FILTER")
            .long("exclude")
            .multiple(true)
            .help("Exclude files matching this glob expression"))*/
        .get_matches();

    fn get_parameter<'a, T>(matches: &clap::ArgMatches, name: &str, default_value: T) -> Result<T>
        where T: std::str::FromStr
    {
        match matches.value_of(name) {
            Some(v) => {
                Ok(v.parse().map_err(|_| ErrorKind::InvalidParameterValue(name.to_owned()))?)
            }
            None => Ok(default_value),
        }
    }

    let case_sensitive = !matches.is_present("nocase");
    let compressor = get_parameter(&matches, "compressor", CompressionAlgorithm::GZip)?;
    let temp = Parameters {
        extension: matches.value_of("ext")
            .unwrap_or(compressor.extension())
            .trim_matches(|c: char| c.is_whitespace() || c.is_control() || c == '.')
            .to_owned(),
        compressor: compressor,
        threads: get_parameter(&matches, "threads", 1)?,
    };

    /*let exclude_filters = match matches.values_of("exclude") {
        Some(values)=> values.map(|s| s.to_owned()).collect(),
        None => Vec::<String>::new(),
    };*/

    let parameters = Arc::<Parameters>::new(temp);
    let (send_queue, stats_rx, wait_group) = start_workers(&parameters);

    let include_filters: Vec<String> = match matches.values_of("filters") {
        Some(values) => Ok(values.map(|s| s.to_owned()).collect()),
        None => Err(ErrorKind::InvalidUsage),
    }?;

    let mut builder = GlobSetBuilder::new();
    for filter in include_filters.iter() {
        let glob = GlobBuilder::new(filter)
            .case_insensitive(!case_sensitive)
            .literal_separator(true)
            .build().map_err(|_| ErrorKind::InvalidIncludeFilter)?;
        builder.add(glob);
    }
    let globset = builder.build().map_err(|_| ErrorKind::InvalidIncludeFilter)?;

    //convert filters to paths and deal out conversion jobs
    dispatch_jobs(send_queue, globset/*, exclude_filters*/)?;

    //wait for all jobs to finish
    wait_group.wait();

    //merge statistics from all threads
    let mut stats = Statistics::new();
    while let Ok(thread_stats) = stats_rx.recv() {
        stats.merge(&thread_stats);
    }

    println!("{}", stats);

    Ok(())
}

type ThreadParam = std::path::PathBuf;

fn start_workers<'a>(params: &Arc<Parameters>) -> (chan::Sender<ThreadParam>, mpsc::Receiver<Statistics>, chan::WaitGroup) {
    let (tx, rx) = chan::sync::<ThreadParam>(params.threads);
    let (stats_tx, stats_rx) = std::sync::mpsc::channel::<Statistics>();
    let wg = chan::WaitGroup::new();

    for _ in 0..params.threads {
        let local_params = params.clone();
        let local_rx = rx.clone();
        let local_stats_tx = stats_tx.clone();
        let local_wg = wg.clone();
        wg.add(1);
        std::thread::spawn(move || {
            worker_thread(local_params, local_stats_tx, local_rx);
            local_wg.done();
        });
    }

    (tx, stats_rx, wg)
}

fn yield_file<F>(path: PathBuf, globset: &GlobSet, callback: &F) -> Result<()>
    where F: Fn(PathBuf) -> Result<()>
{
    if is_hidden(&path)? {
        //we are ignoring .files and .directories
        //we may add a command-line switch to control this behavior in the future
        return Ok(());
    }

    if path.is_dir() {
        for child in path.read_dir()? {
            let child_path = child?.path();
            yield_file(child_path, globset, callback)?;
        }
    }
    else {
        //I'm presuming the binary search in is_blacklisted is faster
        //than globset.is_match, but we should benchmark it at some point
        if !is_blacklisted(&path)? && globset.is_match(&path) {
            callback(path)?;
        }
    }

    Ok(())
}

fn dispatch_jobs(send_queue: chan::Sender<ThreadParam>, globset: GlobSet/*, exclude_filters: Vec<String>*/) -> Result<()> {
    yield_file(Path::new("./").to_path_buf(), &globset, &|path: PathBuf| {
        send_queue.send(path);
        Ok(())
    })
}

fn worker_thread(params: Arc<Parameters>, stats_tx: mpsc::Sender<Statistics>, rx: chan::Receiver<ThreadParam>) {
    let mut local_stats = Statistics::new();

    loop {
        let src = match rx.recv() {
            Some(task) => task,
            None => break, //no more tasks
        };

        //in a nested function so we can handle errors centrally
        fn compress_single(src: &ThreadParam, params: &Parameters, mut local_stats: &mut Statistics) -> Result<()> {
            let dst_path = format!("{}.{}",
                                   src.to_str().ok_or(ErrorKind::InvalidCharactersInPath)?,
                                   params.extension);
            let dst = Path::new(&dst_path);

            //again, in a scope for error handling
            |local_stats: &mut Statistics| -> Result<()> {
                    println!("{}", src.to_string_lossy());
                    let src_metadata = std::fs::metadata(src)?;

                    //don't compress files that are already compressed that haven't changed
                    if let Ok(dst_metadata) = std::fs::metadata(dst) {
                        //the destination already exists
                        let src_seconds = src_metadata.modified()?.duration_since(std::time::UNIX_EPOCH)?.as_secs();
                        let dst_seconds = dst_metadata.modified()?.duration_since(std::time::UNIX_EPOCH)?.as_secs();
                        match src_seconds == dst_seconds {
                            true => {
                                local_stats.update(src_metadata.len(), dst_metadata.len(), false);
                                return Ok(());//no need to recompress
                            },
                            false => {
                                std::fs::remove_file(dst)?; //throw if we can't
                            }
                        };
                    }
                    params.compressor.compress(src.as_path(), dst)?;
                    let dst_metadata = std::fs::metadata(dst)?;
                    local_stats.update(src_metadata.len(), dst_metadata.len(), true);
                    let src_modified = filetime::FileTime::from_last_modification_time(&src_metadata);
                    filetime::set_file_times(dst, filetime::FileTime::zero(), src_modified).unwrap_or_default();

                    Ok(())
                }(&mut local_stats)
                .map_err(|e| {
                    //try deleting the invalid destination file, but don't care if we can't
                    std::fs::remove_file(dst).unwrap_or_default();
                    e //return the same error
                })
        }

        if let Err(e) = compress_single(&src, &params, &mut local_stats) {
            errstln!("Error compressing {}: {}", src.to_string_lossy(), e);
        }
    }

    if !stats_tx.send(local_stats).is_ok() {
        errstln!("Error compiling statistics!");
    }
}

fn str_search(sorted: &[&str], search_term: &str, case_sensitive: bool) -> std::result::Result<usize, usize> {
    let term = match case_sensitive {
        true => search_term.to_owned(),
        false => search_term.to_lowercase(),
    };

    sorted.binary_search_by(|probe| probe.cmp(&&*term))
}

fn is_hidden(path: &Path) -> Result<bool> {
    let hidden = match path.file_name() {
        Some(x) => x.to_str().ok_or(ErrorKind::InvalidCharactersInPath)?
            .starts_with("."),
        None => false
    };
    Ok(hidden)
}

fn is_blacklisted(path: &Path) -> Result<bool> {

    let r = match path.extension() {
        Some(x) => {
            let ext = x.to_str().ok_or(ErrorKind::InvalidCharactersInPath)?;
            str_search(COMP_EXTS, &ext, false).is_ok()
        },
        None => false,
    };

    return Ok(r);
}

<commit_msg>Completely new path search algorithm<commit_after>#[macro_use] extern crate error_chain;
#[macro_use] extern crate prettytable;
#[macro_use] extern crate stderr;
extern crate chan;
extern crate clap;
extern crate filetime;
extern crate globset;
extern crate pretty_bytes;
extern crate separator;

#[macro_use] mod errors;
mod compression;
mod lists;
mod structs;

use clap::{App, Arg};
use errors::*;
use globset::{GlobBuilder, GlobSet, GlobSetBuilder};
use lists::*;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::mpsc;
use structs::*;

const DEBUG_FILTERS: bool = cfg!(debug_assertions);

quick_main!(run);

fn run() -> Result<()> {
    let matches = App::new("static-compress")
        .version("0.1")
        .about("Create statically-compresed copies of matching files")
        .author("NeoSmart Technologies")
        .arg(Arg::with_name("compressor")
            .short("c")
            .long("compressor")
            .value_name("[brotli|gzip|zopfli]")
            .help("The compressor to use (default: gzip)")
            .takes_value(true))
        .arg(Arg::with_name("threads")
            .short("j")
            .long("threads")
            .value_name("COUNT")
            .help("The number of simultaneous compressions (default: 1)")
            .takes_value(true))
        .arg(Arg::with_name("filters")
            .value_name("FILTER")
            .multiple(true)
            .required(true))
        .arg(Arg::with_name("ext")
            .short("e")
            .value_name("EXT")
            .long("extension")
            .help("The extension to use for compressed files (default: gz or br)"))
        /*.arg(Arg::with_name("excludes")
            .short("x")
            .value_name("FILTER")
            .long("exclude")
            .multiple(true)
            .help("Exclude files matching this glob expression"))*/
        .get_matches();

    fn get_parameter<'a, T>(matches: &clap::ArgMatches, name: &str, default_value: T) -> Result<T>
        where T: std::str::FromStr
    {
        match matches.value_of(name) {
            Some(v) => {
                Ok(v.parse().map_err(|_| ErrorKind::InvalidParameterValue(name.to_owned()))?)
            }
            None => Ok(default_value),
        }
    }

    let case_sensitive = !matches.is_present("nocase");
    let compressor = get_parameter(&matches, "compressor", CompressionAlgorithm::GZip)?;
    let temp = Parameters {
        extension: matches.value_of("ext")
            .unwrap_or(compressor.extension())
            .trim_matches(|c: char| c.is_whitespace() || c.is_control() || c == '.')
            .to_owned(),
        compressor: compressor,
        threads: get_parameter(&matches, "threads", 1)?,
    };

    /*let exclude_filters = match matches.values_of("exclude") {
        Some(values)=> values.map(|s| s.to_owned()).collect(),
        None => Vec::<String>::new(),
    };*/

    let parameters = Arc::<Parameters>::new(temp);
    let (send_queue, stats_rx, wait_group) = start_workers(&parameters);

    let include_filters: Vec<String> = match matches.values_of("filters") {
        Some(values) => Ok(values.map(|s| s.to_owned()).collect()),
        None => Err(ErrorKind::InvalidUsage),
    }?;

    let mut builder = GlobSetBuilder::new();
    for filter in include_filters.iter() {
        let glob = GlobBuilder::new(filter)
            .case_insensitive(!case_sensitive)
            .literal_separator(true)
            .build().map_err(|_| ErrorKind::InvalidIncludeFilter)?;
        builder.add(glob);
    }
    let globset = builder.build().map_err(|_| ErrorKind::InvalidIncludeFilter)?;

    //convert filters to paths and deal out conversion jobs

    dispatch_jobs(send_queue, include_filters, globset/*, exclude_filters*/)?;

    //wait for all jobs to finish
    wait_group.wait();

    //merge statistics from all threads
    let mut stats = Statistics::new();
    while let Ok(thread_stats) = stats_rx.recv() {
        stats.merge(&thread_stats);
    }

    println!("{}", stats);

    Ok(())
}

type ThreadParam = std::path::PathBuf;

fn start_workers<'a>(params: &Arc<Parameters>) -> (chan::Sender<ThreadParam>, mpsc::Receiver<Statistics>, chan::WaitGroup) {
    let (tx, rx) = chan::sync::<ThreadParam>(params.threads);
    let (stats_tx, stats_rx) = std::sync::mpsc::channel::<Statistics>();
    let wg = chan::WaitGroup::new();

    for _ in 0..params.threads {
        let local_params = params.clone();
        let local_rx = rx.clone();
        let local_stats_tx = stats_tx.clone();
        let local_wg = wg.clone();
        wg.add(1);
        std::thread::spawn(move || {
            worker_thread(local_params, local_stats_tx, local_rx);
            local_wg.done();
        });
    }

    (tx, stats_rx, wg)
}

fn yield_file<F>(path: PathBuf, globset: &GlobSet, callback: &F) -> Result<()>
    where F: Fn(PathBuf) -> Result<()>
{
    if is_hidden(&path)? {
        //we are ignoring .files and .directories
        //we may add a command-line switch to control this behavior in the future
        return Ok(());
    }

    if path.is_dir() {
        for child in path.read_dir()? {
            let child_path = child?.path();
            yield_file(child_path, globset, callback)?;
        }
    }
    else {
        //I'm presuming the binary search in is_blacklisted is faster
        //than globset.is_match, but we should benchmark it at some point
        if !is_blacklisted(&path)? && globset.is_match(&path) {
            callback(path)?;
        }
    }

    Ok(())
}

fn dispatch_jobs(send_queue: chan::Sender<ThreadParam>, filters: Vec<String>, globset: GlobSet/*, exclude_filters: Vec<String>*/) -> Result<()> {
    let paths = extract_paths(&filters)?;
    for path in paths {
        yield_file(path, &globset, &|path: PathBuf| {
            send_queue.send(path);
            Ok(())
        })?
    }

    Ok(())
}

fn worker_thread(params: Arc<Parameters>, stats_tx: mpsc::Sender<Statistics>, rx: chan::Receiver<ThreadParam>) {
    let mut local_stats = Statistics::new();

    loop {
        let src = match rx.recv() {
            Some(task) => task,
            None => break, //no more tasks
        };

        //in a nested function so we can handle errors centrally
        fn compress_single(src: &ThreadParam, params: &Parameters, mut local_stats: &mut Statistics) -> Result<()> {
            let dst_path = format!("{}.{}",
                                   src.to_str().ok_or(ErrorKind::InvalidCharactersInPath)?,
                                   params.extension);
            let dst = Path::new(&dst_path);

            //again, in a scope for error handling
            |local_stats: &mut Statistics| -> Result<()> {
                    println!("{}", src.to_string_lossy());
                    let src_metadata = std::fs::metadata(src)?;

                    //don't compress files that are already compressed that haven't changed
                    if let Ok(dst_metadata) = std::fs::metadata(dst) {
                        //the destination already exists
                        let src_seconds = src_metadata.modified()?.duration_since(std::time::UNIX_EPOCH)?.as_secs();
                        let dst_seconds = dst_metadata.modified()?.duration_since(std::time::UNIX_EPOCH)?.as_secs();
                        match src_seconds == dst_seconds {
                            true => {
                                local_stats.update(src_metadata.len(), dst_metadata.len(), false);
                                return Ok(());//no need to recompress
                            },
                            false => {
                                std::fs::remove_file(dst)?; //throw if we can't
                            }
                        };
                    }
                    params.compressor.compress(src.as_path(), dst)?;
                    let dst_metadata = std::fs::metadata(dst)?;
                    local_stats.update(src_metadata.len(), dst_metadata.len(), true);
                    let src_modified = filetime::FileTime::from_last_modification_time(&src_metadata);
                    filetime::set_file_times(dst, filetime::FileTime::zero(), src_modified).unwrap_or_default();

                    Ok(())
                }(&mut local_stats)
                .map_err(|e| {
                    //try deleting the invalid destination file, but don't care if we can't
                    std::fs::remove_file(dst).unwrap_or_default();
                    e //return the same error
                })
        }

        if let Err(e) = compress_single(&src, &params, &mut local_stats) {
            errstln!("Error compressing {}: {}", src.to_string_lossy(), e);
        }
    }

    if !stats_tx.send(local_stats).is_ok() {
        errstln!("Error compiling statistics!");
    }
}

fn str_search(sorted: &[&str], search_term: &str, case_sensitive: bool) -> std::result::Result<usize, usize> {
    let term = match case_sensitive {
        true => search_term.to_owned(),
        false => search_term.to_lowercase(),
    };

    sorted.binary_search_by(|probe| probe.cmp(&&*term))
}

fn is_hidden(path: &Path) -> Result<bool> {
    let hidden = match path.file_name() {
        Some(x) => x.to_str().ok_or(ErrorKind::InvalidCharactersInPath)?
            .starts_with("."),
        None => false
    };
    Ok(hidden)
}

fn is_blacklisted(path: &Path) -> Result<bool> {

    let r = match path.extension() {
        Some(x) => {
            let ext = x.to_str().ok_or(ErrorKind::InvalidCharactersInPath)?;
            str_search(COMP_EXTS, &ext, false).is_ok()
        },
        None => false,
    };

    return Ok(r);
}

//Given a list of filters, extracts the directories that should be searched
//To-Do: Also provide info about to what depth they should be recursed
use std::collections::HashSet;
fn extract_paths(filters: &Vec<String>) -> Result<HashSet<PathBuf>> {
    use std::iter::FromIterator;

    let mut dirs = std::collections::HashSet::<PathBuf>::new();

    {
        let insert_path = &mut |filter: &String, dir: PathBuf| {
            if DEBUG_FILTERS {
                println!("filter {} mapped to ./", filter);
            }
            dirs.insert(dir);
        };

        for filter in filters {
            //take everything until the first expression
            let mut last_char = None::<char>;
            let dir;
            {
                let partial = filter.chars().take_while(|c| match c {
                    &'?' | &'*' | &'{' | &'[' => false,
                    c => { last_char = Some(c.clone()); true }
                });
                dir = String::from_iter(partial);
            }

            let dir = match dir.chars().next() {
                Some(c) => match c {
                    '.' | '/' => PathBuf::from(dir),
                    _ => {
                        let mut pb = PathBuf::from("./");
                        pb.push(dir);
                        pb
                    }
                },
                None => {
                    insert_path(filter, PathBuf::from("./"));
                    continue;
                }
            };

            if dir.to_str().ok_or(ErrorKind::InvalidCharactersInPath)?.ends_with(filter) {
                //the "dir" is actually a full path to a single file
                //return it as-is
                insert_path(filter, dir);
                continue;
            }

            if last_char == Some('/') {
                //dir is a already a directory, return it as-is
                insert_path(filter, dir);
                continue;
            }

            //we need to extract the directory from the path we have
            let dir = match PathBuf::from(dir).parent() {
                Some(parent) => parent.to_path_buf(),
                None => PathBuf::from("./"),
            };

            insert_path(filter, dir);
        }
    }

    if DEBUG_FILTERS {
        println!("final dirs: {:?}", dirs);
    }

    Ok(dirs)
}
<|endoftext|>


<commit_before>#![feature(collections, core, env, libc, old_io, old_path, plugin, std_misc)]

// Other platforms than macos don't need std_misc but you can't
// use #[cfg] on features.
#![allow(unused_features)]

extern crate ansi_term;
extern crate datetime;
extern crate getopts;
extern crate locale;
extern crate natord;
extern crate number_prefix;
extern crate pad;
extern crate users;

#[cfg(feature="git")]
extern crate git2;

use std::env;
use std::old_io::{fs, FileType};
use std::path::Component::CurDir;

use dir::Dir;
use file::File;
use options::{Options, View};
use output::lines_view;

pub mod column;
pub mod dir;
pub mod file;
pub mod filetype;
pub mod options;
pub mod output;
pub mod term;
pub mod xattr;

struct Exa<'a> {
    count:   usize,
    options: Options,
    dirs:    Vec<Path>,
    files:   Vec<File<'a>>,
}

impl<'a> Exa<'a> {
    fn new(options: Options) -> Exa<'a> {
        Exa {
            count: 0,
            options: options,
            dirs: Vec::new(),
            files: Vec::new(),
        }
    }

    fn load<T>(&mut self, iter: T) where T: Iterator<Item = &'a String> {
        // Separate the user-supplied paths into directories and files.
        // Files are shown first, and then each directory is expanded
        // and listed second.
        for file in iter {
            let path = Path::new(file);
            match fs::stat(&path) {
                Ok(stat) => {
                    if stat.kind == FileType::Directory {
                        if self.options.dir_action.is_tree() {
                            self.files.push(File::with_stat(stat, &path, None, true));
                        }
                        else {
                            self.dirs.push(path);
                        }
                    }
                    else {
                        self.files.push(File::with_stat(stat, &path, None, false));
                    }
                }
                Err(e) => println!("{}: {}", file, e),
            }

            self.count += 1;
        }
    }

    fn print_files(&self) {
        if !self.files.is_empty() {
            self.print(None, &self.files[..]);
        }
    }

    fn print_dirs(&mut self) {
        let mut first = self.files.is_empty();

        // Directories are put on a stack rather than just being iterated through,
        // as the vector can change as more directories are added.
        loop {
            let dir_path = match self.dirs.pop() {
                None => break,
                Some(f) => f,
            };

            // Put a gap between directories, or between the list of files and the
            // first directory.
            if first {
                first = false;
            }
            else {
                print!("\n");
            }

            match Dir::readdir(&dir_path) {
                Ok(ref dir) => {
                    let mut files = dir.files(false);
                    self.options.transform_files(&mut files);

                    // When recursing, add any directories to the dirs stack
                    // backwards: the *last* element of the stack is used each
                    // time, so by inserting them backwards, they get displayed in
                    // the correct sort order.
                    if let Some(recurse_opts) = self.options.dir_action.recurse_options() {
                        if !recurse_opts.tree && !recurse_opts.is_too_deep(dir_path.components().count() + 1) {
                            for dir in files.iter().filter(|f| f.stat.kind == FileType::Directory).rev() {
                                self.dirs.push(dir.path.clone());
                            }
                        }
                    }

                    if self.count > 1 {
                        println!("{}:", dir_path.display());
                    }
                    self.count += 1;

                    self.print(Some(dir), &files[..]);
                }
                Err(e) => {
                    println!("{}: {}", dir_path.display(), e);
                    return;
                }
            };
        }
    }

    fn print(&self, dir: Option<&Dir>, files: &[File]) {
        match self.options.view {
            View::Grid(g)     => g.view(files),
            View::Details(d)  => d.view(dir, files),
            View::Lines       => lines_view(files),
        }
    }
}

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

    match Options::getopts(args.tail()) {
        Ok((options, paths)) => {
            let mut exa = Exa::new(options);
            exa.load(paths.iter());
            exa.print_files();
            exa.print_dirs();
        },
        Err(e) => {
            println!("{}", e);
            env::set_exit_status(e.error_code());
        },
    };
}
<commit_msg>Fix bug where the wrong level was used for . dir<commit_after>#![feature(collections, core, env, libc, old_io, old_path, plugin, std_misc)]

// Other platforms than macos don't need std_misc but you can't
// use #[cfg] on features.
#![allow(unused_features)]

extern crate ansi_term;
extern crate datetime;
extern crate getopts;
extern crate locale;
extern crate natord;
extern crate number_prefix;
extern crate pad;
extern crate users;

#[cfg(feature="git")]
extern crate git2;

use std::env;
use std::old_io::{fs, FileType};

use dir::Dir;
use file::File;
use options::{Options, View};
use output::lines_view;

pub mod column;
pub mod dir;
pub mod file;
pub mod filetype;
pub mod options;
pub mod output;
pub mod term;
pub mod xattr;

struct Exa<'a> {
    count:   usize,
    options: Options,
    dirs:    Vec<Path>,
    files:   Vec<File<'a>>,
}

impl<'a> Exa<'a> {
    fn new(options: Options) -> Exa<'a> {
        Exa {
            count: 0,
            options: options,
            dirs: Vec::new(),
            files: Vec::new(),
        }
    }

    fn load<T>(&mut self, iter: T) where T: Iterator<Item = &'a String> {
        // Separate the user-supplied paths into directories and files.
        // Files are shown first, and then each directory is expanded
        // and listed second.
        for file in iter {
            let path = Path::new(file);
            match fs::stat(&path) {
                Ok(stat) => {
                    if stat.kind == FileType::Directory {
                        if self.options.dir_action.is_tree() {
                            self.files.push(File::with_stat(stat, &path, None, true));
                        }
                        else {
                            self.dirs.push(path);
                        }
                    }
                    else {
                        self.files.push(File::with_stat(stat, &path, None, false));
                    }
                }
                Err(e) => println!("{}: {}", file, e),
            }

            self.count += 1;
        }
    }

    fn print_files(&self) {
        if !self.files.is_empty() {
            self.print(None, &self.files[..]);
        }
    }

    fn print_dirs(&mut self) {
        let mut first = self.files.is_empty();

        // Directories are put on a stack rather than just being iterated through,
        // as the vector can change as more directories are added.
        loop {
            let dir_path = match self.dirs.pop() {
                None => break,
                Some(f) => f,
            };

            // Put a gap between directories, or between the list of files and the
            // first directory.
            if first {
                first = false;
            }
            else {
                print!("\n");
            }

            match Dir::readdir(&dir_path) {
                Ok(ref dir) => {
                    let mut files = dir.files(false);
                    self.options.transform_files(&mut files);

                    // When recursing, add any directories to the dirs stack
                    // backwards: the *last* element of the stack is used each
                    // time, so by inserting them backwards, they get displayed in
                    // the correct sort order.
                    if let Some(recurse_opts) = self.options.dir_action.recurse_options() {
                        let depth = dir_path.components().filter(|&c| c != b".").count() + 1;
                        if !recurse_opts.tree && !recurse_opts.is_too_deep(depth) {
                            for dir in files.iter().filter(|f| f.stat.kind == FileType::Directory).rev() {
                                self.dirs.push(dir.path.clone());
                            }
                        }
                    }

                    if self.count > 1 {
                        println!("{}:", dir_path.display());
                    }
                    self.count += 1;

                    self.print(Some(dir), &files[..]);
                }
                Err(e) => {
                    println!("{}: {}", dir_path.display(), e);
                    return;
                }
            };
        }
    }

    fn print(&self, dir: Option<&Dir>, files: &[File]) {
        match self.options.view {
            View::Grid(g)     => g.view(files),
            View::Details(d)  => d.view(dir, files),
            View::Lines       => lines_view(files),
        }
    }
}

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

    match Options::getopts(args.tail()) {
        Ok((options, paths)) => {
            let mut exa = Exa::new(options);
            exa.load(paths.iter());
            exa.print_files();
            exa.print_dirs();
        },
        Err(e) => {
            println!("{}", e);
            env::set_exit_status(e.error_code());
        },
    };
}
<|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
                        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();
}
<commit_msg>Fix underflow when 'G' 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 => {
                    if buf.len() >= 1 {
                        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|>