<commit_before>extern crate cargo_tarpaulin;
extern crate nix;
extern crate gimli;
extern crate object;
extern crate memmap;
extern crate fallible_iterator;
extern crate rustc_demangle;
#[macro_use]
extern crate clap;

use std::path::Path;
use clap::{App, Arg, SubCommand, ArgSettings};
use cargo_tarpaulin::run;
use cargo_tarpaulin::config::*;

fn is_dir(d: String) -> Result<(), String> {
    if Path::new(&d).is_dir() {
        Ok(())
    } else {
        Err(String::from("root must be a directory"))
    }
}

const CI_SERVER_HELP: &'static str =
"Name of service, supported services are:
travis-ci, travis-pro, circle-ci, semaphore, jenkins and codeship.
If you are interfacing with coveralls.io or another site you can \
also specify a name that they will recognise. Refer to their documentation for this.";

fn main() {
    let args = App::new("cargo-tarpaulin")
        .author("Daniel McKenna, <danielmckenna93@gmail.com>")
        .about("Tool to analyse test coverage of cargo projects")
        .version(concat!("version: ", crate_version!()))
        .bin_name("cargo")
        .subcommand(SubCommand::with_name("tarpaulin")
            .about("Tool to analyse test coverage of cargo projects")
            .version(concat!("version: ", crate_version!()))
            .args_from_usage(
                 "--verbose -v 'Show extra output'
                 --ignore-tests 'ignore lines of test functions when collecting coverage'
                 --ignore-panics 'ignore panic macros in tests'
                 --no-count   'Disables counting line hits for a faster run (default)'
                 --ignored -i 'Run ignored tests as well'
                 --line -l    'Line coverage'
                 --skip-clean 'Skips the clean stage to reduce build times, may affect coverage results'
                 --branch -b  'Branch coverage: NOT IMPLEMENTED'
                 --forward -f 'Forwards unexpected signals to test. Tarpaulin will still take signals it is expecting.'
                 --coveralls [KEY]  'Coveralls key, either the repo token, or if you're using travis use $TRAVIS_JOB_ID and specify travis-{ci|pro} in --ciserver'
                 --report-uri [URI] 'URI to send report to, only used if the option --coveralls is used'
                 --features [FEATURE]... 'Features to be included in the target project'
                 --all-features 'Build all available features'
                 --all        'Build all packages in the workspace'
                 --packages -p [PACKAGE]... 'Package id specifications for which package should be build. See cargo help pkgid for more info'
                 --exclude -e [PACKAGE]... 'Package id specifications to exclude from coverage. See cargo help pkgid for more info'
                 --exclude-files [FILE]... 'Exclude given files from coverage results has * wildcard'
                 --timeout -t [SECONDS] 'Integer for the maximum time in seconds without response from test before timeout (default is 1 minute).'")
            .args(&[
                Arg::from_usage("--count 'Counts the number of hits during line coverage'")
                    .conflicts_with("no-count"),
                Arg::from_usage("--out -o [FMT]   'Output path'")
                    .possible_values(&OutputFile::variants())
                    .multiple(true),
                Arg::from_usage("--root -r [DIR]  'Root directory containing Cargo.toml to use'")
                    .validator(is_dir),
                Arg::from_usage("--ciserver [SERVICE] 'CI server being used'")
                    .help(CI_SERVER_HELP),
                Arg::with_name("args")
                    .set(ArgSettings::Last)
                    .multiple(true)
                    .help("Arguments to be passed to the test executables can be used to filter or skip certain tests")
            ]))
        .get_matches();

    let args = args.subcommand_matches("tarpaulin").unwrap_or(&args);
    let config = Config::from_args(args);
    match run(&config) {
        Ok(()) => println!("Tarpaulin finished"),
        Err(e) => {
            println!("Error during run");
            std::process::exit(e);
        },
    }
}
<commit_msg>Fixes #125<commit_after>extern crate cargo_tarpaulin;
extern crate nix;
extern crate gimli;
extern crate object;
extern crate memmap;
extern crate fallible_iterator;
extern crate rustc_demangle;
#[macro_use]
extern crate clap;

use std::path::Path;
use clap::{App, Arg, SubCommand, ArgSettings};
use cargo_tarpaulin::run;
use cargo_tarpaulin::config::*;

fn is_dir(d: String) -> Result<(), String> {
    if Path::new(&d).is_dir() {
        Ok(())
    } else {
        Err(String::from("root must be a directory"))
    }
}

const CI_SERVER_HELP: &'static str =
"Name of service, supported services are:
travis-ci, travis-pro, circle-ci, semaphore, jenkins and codeship.
If you are interfacing with coveralls.io or another site you can \
also specify a name that they will recognise. Refer to their documentation for this.";

fn main() {
    let args = App::new("cargo-tarpaulin")
        .author("Daniel McKenna, <danielmckenna93@gmail.com>")
        .about("Tool to analyse test coverage of cargo projects")
        .version(concat!("version: ", crate_version!()))
        .bin_name("cargo")
        .subcommand(SubCommand::with_name("tarpaulin")
            .about("Tool to analyse test coverage of cargo projects")
            .version(concat!("version: ", crate_version!()))
            .args_from_usage(
                 "--verbose -v 'Show extra output'
                 --ignore-tests 'ignore lines of test functions when collecting coverage'
                 --ignore-panics 'ignore panic macros in tests'
                 --no-count   'Disables counting line hits for a faster run (default)'
                 --ignored -i 'Run ignored tests as well'
                 --line -l    'Line coverage'
                 --skip-clean 'Skips the clean stage to reduce build times, may affect coverage results'
                 --branch -b  'Branch coverage: NOT IMPLEMENTED'
                 --forward -f 'Forwards unexpected signals to test. Tarpaulin will still take signals it is expecting.'
                 --coveralls [KEY]  'Coveralls key, either the repo token, or if you're using travis use $TRAVIS_JOB_ID and specify travis-{ci|pro} in --ciserver'
                 --report-uri [URI] 'URI to send report to, only used if the option --coveralls is used'
                 --features [FEATURE]... 'Features to be included in the target project'
                 --all-features 'Build all available features'
                 --all        'Build all packages in the workspace'
                 --packages -p [PACKAGE]... 'Package id specifications for which package should be build. See cargo help pkgid for more info'
                 --exclude -e [PACKAGE]... 'Package id specifications to exclude from coverage. See cargo help pkgid for more info'
                 --exclude-files [FILE]... 'Exclude given files from coverage results has * wildcard'
                 --timeout -t [SECONDS] 'Integer for the maximum time in seconds without response from test before timeout (default is 1 minute).'")
            .args(&[
                Arg::from_usage("--count 'Counts the number of hits during line coverage'")
                    .conflicts_with("no-count"),
                Arg::from_usage("--out -o [FMT]   'Output format of coverage report'")
                    .possible_values(&OutputFile::variants())
                    .multiple(true),
                Arg::from_usage("--root -r [DIR]  'Root directory containing Cargo.toml to use'")
                    .validator(is_dir),
                Arg::from_usage("--ciserver [SERVICE] 'CI server being used'")
                    .help(CI_SERVER_HELP),
                Arg::with_name("args")
                    .set(ArgSettings::Last)
                    .multiple(true)
                    .help("Arguments to be passed to the test executables can be used to filter or skip certain tests")
            ]))
        .get_matches();

    let args = args.subcommand_matches("tarpaulin").unwrap_or(&args);
    let config = Config::from_args(args);
    match run(&config) {
        Ok(()) => println!("Tarpaulin finished"),
        Err(e) => {
            println!("Error during run");
            std::process::exit(e);
        },
    }
}
<|endoftext|>


<commit_before>extern crate rustc_serialize;
extern crate docopt;

use docopt::Docopt;

static USAGE: &'static str = "
Usage: seax [-v] <bin>

Options:
    -v, --verbose   Enable verbose mode
";

#[derive(RustcDecodable, Debug)]
struct Args {
    arg_bin: String,
    flag_verbose: bool
}

fn main() {
    let args: Args = Docopt::new(USAGE)
                            .and_then(|d| d.decode())
                            .unwrap_or_else(|e| e.exit());
    println!("{:?}", args);
}
<commit_msg>Started on REPL<commit_after>#![feature(scheme)]
#![feature(compile)]
#![feature(convert)]
extern crate rustc_serialize;
extern crate docopt;

extern crate seax_svm as svm;
extern crate seax_scheme as scheme;

use docopt::Docopt;
use std::io;
use std::io::{Read, BufRead};

static USAGE: &'static str = "
Usage:
    seax repl [-vd]
    seax [-vd] <bin>

Options:
    -v, --verbose   Enable verbose mode
    -d, --debug     Enable debug mode
";

#[derive(RustcDecodable)]
struct Args {
    cmd_repl: bool,
    arg_bin: String,
    flag_verbose: bool,
    flag_debug: bool,
}

fn main() {
    let args: Args = Docopt::new(USAGE)
                .and_then(|d| d.decode())
                .unwrap_or_else(|e| e.exit());
    if args.cmd_repl {
        let mut console = io::stdin();
        let lock = console.lock();
        for line in lock.lines() {
            println!(">");
            match line {
                Ok(line) => { scheme::compile(line.as_ref())
                                .and_then(  |p| Ok(svm::eval_program(p, args.flag_debug)) )
                                .map(       |r| println!("{}", r) )
                                .map_err(   |e| println!("{}", e) ); },
                Err(why) => println!("{}", why)

            }
        }
    }
}
<|endoftext|>


<commit_before>extern crate iron;
#[macro_use]
extern crate router;
extern crate persistent;
extern crate url;
extern crate rustc_serialize;
extern crate unidecode;
extern crate unicode_segmentation;
#[macro_use]
extern crate log;

mod api;
mod term;
mod token;
mod document;
mod index;
mod query;
mod mapping;
mod analysis;
mod search;
mod logger;

use std::sync::RwLock;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::fs;

use iron::prelude::*;
use iron::typemap::Key;

use index::Index;

const VERSION: &'static str = "0.1a0";

struct Globals {
    pub indices_path: PathBuf,
    pub indices: RwLock<HashMap<String, Index>>,
}

impl Globals {
    fn new(indices_path: PathBuf, indices: HashMap<String, Index>) -> Globals {
        Globals {
            indices_path: indices_path,
            indices: RwLock::new(indices),
        }
    }
}

impl Key for Globals {
    type Value = Globals;
}

fn load_index(path: &Path) -> Index {
    Index::new()
}

fn load_indices(indices_path: &Path) -> HashMap<String, Index> {
    let mut indices = HashMap::new();

    for file in fs::read_dir(&indices_path).unwrap() {
        let path = file.unwrap().path();
        let index_name: String = path.file_stem().unwrap().to_str().unwrap().to_owned();

        if let Some(ext) = path.extension() {
            if ext.to_str() == Some("rsi") {
                info!("Loaded index: {}", index_name);
                indices.insert(index_name, load_index(path.as_path()));
            }
        }
    }

    indices
}

fn main() {
    println!("rusticsearch ({})", VERSION);
    println!("");

    println!("{:?}",
             analysis::Analyzer::EdgeNGram.run("Up from the bowels of hell he sail. Weilding a \
                                                tankard of freshly brewed ale."
                                                   .to_string()));

    logger::init().unwrap();

    let indices_path = Path::new("./indices").to_path_buf();
    let indices = load_indices(&indices_path.as_path());
    let router = api::get_router();
    let mut chain = Chain::new(router);
    chain.link(persistent::Read::<Globals>::both(Globals::new(indices_path, indices)));
    Iron::new(chain).http("localhost:9200").unwrap();
}
<commit_msg>Removed analyzer test from main function<commit_after>extern crate iron;
#[macro_use]
extern crate router;
extern crate persistent;
extern crate url;
extern crate rustc_serialize;
extern crate unidecode;
extern crate unicode_segmentation;
#[macro_use]
extern crate log;

mod api;
mod term;
mod token;
mod document;
mod index;
mod query;
mod mapping;
mod analysis;
mod search;
mod logger;

use std::sync::RwLock;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::fs;

use iron::prelude::*;
use iron::typemap::Key;

use index::Index;

const VERSION: &'static str = "0.1a0";

struct Globals {
    pub indices_path: PathBuf,
    pub indices: RwLock<HashMap<String, Index>>,
}

impl Globals {
    fn new(indices_path: PathBuf, indices: HashMap<String, Index>) -> Globals {
        Globals {
            indices_path: indices_path,
            indices: RwLock::new(indices),
        }
    }
}

impl Key for Globals {
    type Value = Globals;
}

fn load_index(path: &Path) -> Index {
    Index::new()
}

fn load_indices(indices_path: &Path) -> HashMap<String, Index> {
    let mut indices = HashMap::new();

    for file in fs::read_dir(&indices_path).unwrap() {
        let path = file.unwrap().path();
        let index_name: String = path.file_stem().unwrap().to_str().unwrap().to_owned();

        if let Some(ext) = path.extension() {
            if ext.to_str() == Some("rsi") {
                info!("Loaded index: {}", index_name);
                indices.insert(index_name, load_index(path.as_path()));
            }
        }
    }

    indices
}

fn main() {
    println!("rusticsearch ({})", VERSION);
    println!("");

    logger::init().unwrap();

    let indices_path = Path::new("./indices").to_path_buf();
    let indices = load_indices(&indices_path.as_path());
    let router = api::get_router();
    let mut chain = Chain::new(router);
    chain.link(persistent::Read::<Globals>::both(Globals::new(indices_path, indices)));
    Iron::new(chain).http("localhost:9200").unwrap();
}
<|endoftext|>


<commit_before>#![feature(collections)]
#![feature(into_cow)]
#![feature(iter_arith)]
use std::fs::File;
use std::io::{BufReader, BufRead, Write};
use std::collections::{HashMap, BinaryHeap};
use std::borrow::{Cow, IntoCow};

use std::env;
use std::io;
use std::cmp;

const WHITESPACE_FACTOR: isize = 5;
const WHITESPACE_REDUCE: isize = 2;
const CLASS_FACTOR: isize = 3;
const FIRST_FACTOR: isize = 3;
const CLASS_REDUCE: isize = 2;

const DIST_WEIGHT: isize = -10;
const HEAT_WEIGHT: isize = 5;
const FACTOR_REDUCE: isize = 50;

const MAX_LEN: usize = 80;

const MATCH_NUMBER: usize = 10;

#[derive(Debug)]
struct LineInfo {
    char_map: HashMap<char, Vec<usize>>,
    heatmap: Vec<isize>,
    pub factor: isize
}

#[derive(Debug)]
struct LineMatch {
    score: isize,
    factor: isize,
    line: Cow<'static, str>
}

#[derive(PartialEq)]
enum CharClass {
    Whitespace,
    Numeric,
    Alphabetic,
    First,
    Other
}

impl Ord for LineMatch {
    fn cmp(&self, other: &LineMatch) -> cmp::Ordering {
        match self.score.cmp(&other.score) {
            cmp::Ordering::Equal => self.factor.cmp(&other.factor),
            order => order
        }
    }
}

impl PartialOrd for LineMatch {
    fn partial_cmp(&self, other: &LineMatch) -> Option<cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl PartialEq for LineMatch {
    fn eq(&self, other: &LineMatch) -> bool {
        self.cmp(other) == cmp::Ordering::Equal
    }
}

impl Eq for LineMatch {}

impl LineInfo {
    fn new<T: AsRef<str>>(item: T, factor: isize) -> LineInfo {
        let mut map: HashMap<char, Vec<usize>> = HashMap::new();
        let mut heat = vec![];
        let line = item.as_ref();

        let mut ws_score = 0;
        let mut cs_score = 0;
        let mut cur_class = CharClass::First;
        // character class changes don't stack
        let mut cs_change = false;

        for (idx, c) in line.chars().enumerate() {
            // don't map whitespace
            if !c.is_whitespace() {
                // update the character class change score if needed
                if cur_class == CharClass::First {
                    // add the first character factor on top of class change
                    cs_score += FIRST_FACTOR;
                }
                if c.is_numeric() {
                    if cur_class != CharClass::Numeric {
                        cur_class = CharClass::Numeric;
                        if !cs_change {
                            cs_score += CLASS_FACTOR;
                            cs_change = true;
                        }
                    } else {
                        cs_change = false;
                    }
                } else if c.is_alphabetic() {
                    if cur_class != CharClass::Alphabetic {
                        cur_class = CharClass::Alphabetic;
                        if !cs_change {
                            cs_score += CLASS_FACTOR;
                            cs_change = true;
                        }
                    } else {
                        cs_change = false;
                    }
                } else {
                    if cur_class != CharClass::Other {
                        cur_class = CharClass::Other;
                        if !cs_change {
                            cs_score += CLASS_FACTOR;
                            cs_change = true;
                        }
                    } else {
                        cs_change = false;
                    }
                }

                // add an entry in the character map
                map.entry(c).or_insert(Vec::new()).push(idx);
                if c.is_uppercase() {
                    for lc in c.to_lowercase() {
                        // also insert all lowercase equivalents of this character
                        // but not the other way around, so that typing something
                        // uppercase specifies to match uppercase
                        map.entry(lc).or_insert(Vec::new()).push(idx);
                    }
                }
            } else {
                // whitespace is treated differently
                cur_class = CharClass::Whitespace;
                ws_score = WHITESPACE_FACTOR;
            }

            // push to the heatmap
            heat.push(ws_score + cs_score);

            // reduce things
            ws_score /= WHITESPACE_REDUCE;
            if !cs_change {
                cs_score /= CLASS_REDUCE;
            }
        }

        LineInfo {
            char_map: map,
            heatmap: heat,
            factor: factor
        }
    }

    fn query_sequence<T: AsRef<str>>(&self, query_item: T) -> Option<Vec<Vec<usize>>> {
        let query = query_item.as_ref();
        let mut positions: Vec<Vec<usize>> = vec![];

        for c in query.chars() {
            match self.char_map.get(&c) {
                None => break,
                Some(list) => {
                    let to_push;
                    match positions.last() {
                        None => {
                            to_push = list.clone();
                        },
                        Some(item) => {
                            match list.binary_search(&item[0]) {
                                Ok(idx) => {
                                    if idx >= list.len() - 1 {
                                        // line is non-matching
                                        break;
                                    } else {
                                        to_push = list.split_at(idx + 1).1.into();
                                    }
                                },
                                Err(idx) => {
                                    if idx >= list.len() {
                                        // line is non-matching
                                        break;
                                    } else {
                                        to_push = list.split_at(idx).1.into();
                                    }
                                }
                            }
                        }
                    }
                    positions.push(to_push);
                }
            }
        }

        if positions.len() == query.len() {
            Some(positions)
        } else {
            None
        }
    }

    fn query_positions<T: AsRef<str>>(&self, query: T) -> Option<Vec<Vec<usize>>> {
        match self.query_sequence(query) {
            None => None,
            Some(positions) => {
                // matching line
                // create our idx vector
                let mut idx = vec![0; positions.len()];
                let mut result = vec![];
                loop {
                    // check that current configuration is strictly increasing
                    let mut ignore = false;
                    {
                        let mut last_pos = None;
                        for (i, pos) in idx.iter().enumerate() {
                            match last_pos {
                                None => last_pos = Some(positions[i][*pos]),
                                Some(other) => {
                                    if other >= positions[i][*pos] {
                                        ignore = true;
                                        break;
                                    } else {
                                        last_pos = Some(positions[i][*pos]);
                                    }
                                }
                            }
                        }
                    }

                    if !ignore {
                        // add the configuration to the list
                        result.push(idx.iter().enumerate().map(|(i, pos)| {positions[i][*pos]}).collect());
                    }

                    // update our position vector
                    let mut update_idx = idx.len() - 1;
                    let mut finished = false;
                    loop {
                        idx[update_idx] += 1;
                        if idx[update_idx] >= positions[update_idx].len() {
                            if update_idx == 0 {
                                // we're finished with all permutations
                                finished = true;
                                break;
                            } else {
                                idx[update_idx] = 0;
                                update_idx -= 1;
                            }
                        } else {
                            // finished updating for this permutation
                            break;
                        }
                    }
                    if finished {
                        // finished with everything
                        break;
                    }
                }

                // return result
                Some(result)
            }
        }
    }

    fn query_score<T: AsRef<str>>(&self, query: T) -> Option<isize> {
        match self.query_positions(query) {
            None => None,
            Some(positions) => {
                let mut top_score = None;
                for pgroup in positions.iter() {
                    // find the average distance between the indexes
                    let mut dist_total = 0;
                    let mut dist_count = 0;
                    for i in 0..pgroup.len() - 1 {
                        dist_total += (pgroup[i + 1] - pgroup[i]) as isize;
                        dist_count += 1;
                    }
                    // sum the heatmap
                    let heat_sum: isize = pgroup.iter().map(|pos| {self.heatmap[*pos]}).sum();
                    let score = (dist_total / dist_count) * DIST_WEIGHT +
                        heat_sum * HEAT_WEIGHT;
                    match top_score {
                        None => top_score = Some(score),
                        Some(last) => {
                            if score > last {
                                top_score = Some(score);
                            }
                        }
                    }
                }

                // return the result
                match top_score {
                    None => None,
                    Some(score) => {
                        Some(score + self.factor / FACTOR_REDUCE)
                    }
                }
            }
        }
    }
}

fn main() {
    let history_path = match env::var("HISTFILE") {
        Ok(p) => p,
        Err(e) => panic!("Failed to get bash history file: {}", e)
    };

    let input_file = match File::open(&history_path) {
        Ok(f) => BufReader::new(f),
        Err(e) => panic!("Could not open history file: {}", e)
    };

    let mut line_number = -1;

    // create a hashmap of lines to info
    let mut lines: HashMap<Cow<str>, LineInfo> = HashMap::new();

    // read the history
    println!("Reading history...");
    for m_line in input_file.lines() {
        let line = match m_line {
            Ok(mut line) => {
                if line.len() > MAX_LEN {
                    let mut cut_at = None;
                    for (idx, _) in line.char_indices() {
                        if idx > MAX_LEN {
                            cut_at = Some(idx);
                            break;
                        }
                    }
                    match cut_at {
                        None => {
                            // do nothing, last character spans the 80th byte
                        },
                        Some(idx) => {
                            line.truncate(idx);
                        }
                    }
                }
                // return the result
                line
            },
            Err(e) => panic!("Failed to read line: {}", e)
        };

        line_number += 1;

        // generate the line info
        let info = LineInfo::new(&line, line_number);

        // insert the line into the map
        lines.insert(line.into_cow(), info);
    }

    let mut query = String::new();

    print!("Match input: ");
    match io::stdout().flush() {
        Err(e) => panic!("Failed to flush stdout: {}", e),
        Ok(_) => {}
    }

    match io::stdin().read_line(&mut query) {
        Ok(_) => {},
        Err(e) => panic!("Failed to read input line: {}", e)
    }

    match query.pop() {
        Some('\n') => {/* pop off trailing newline */},
        Some(c) => query.push(c),
        None => {/* Do nothing with an empty query */}
    }

    // allocate plus one element since we trim after adding one too many
    let mut matches: BinaryHeap<LineMatch> = BinaryHeap::with_capacity(MATCH_NUMBER);

    // search for a match
    for (line, info) in lines.iter() {
        let line_score = match info.query_score(&query) {
            None => {
                // non-matching line
                continue;
            },
            Some(score) => {
                score
            }
        };

        // negate everything so we can use push_pop
        if matches.len() < MATCH_NUMBER {
            matches.push(LineMatch {
                score: -line_score,
                factor: -info.factor,
                line: line.clone()
            });
        } else {
            matches.push_pop(LineMatch {
                score: -line_score,
                factor: -info.factor,
                line: line.clone()
            });
        }
    }

    // result contains a vector of the top MATCH_NUMBER lines, in descending score order
    let result = matches.into_sorted_vec();

    println!("Matches:");

    for item in result.iter() {
        println!("{}", item.line);
    }
}
<commit_msg>Split database out of main<commit_after>#![feature(collections)]
#![feature(into_cow)]
#![feature(iter_arith)]
use std::fs::File;
use std::io::{BufReader, BufRead, Write};
use std::collections::{HashMap, BinaryHeap};
use std::borrow::{Cow, IntoCow, Borrow};
use std::error::Error;

use std::env;
use std::io;
use std::cmp;
use std::fmt;
use std::path;

const WHITESPACE_FACTOR: isize = 5;
const WHITESPACE_REDUCE: isize = 2;
const CLASS_FACTOR: isize = 3;
const FIRST_FACTOR: isize = 3;
const CLASS_REDUCE: isize = 2;

const DIST_WEIGHT: isize = -10;
const HEAT_WEIGHT: isize = 5;
const FACTOR_REDUCE: isize = 50;

const MAX_LEN: usize = 80;

const MATCH_NUMBER: usize = 10;

#[derive(Debug)]
struct LineInfo {
    char_map: HashMap<char, Vec<usize>>,
    heatmap: Vec<isize>,
    pub factor: isize
}

#[derive(Debug)]
struct LineMatch {
    score: isize,
    factor: isize,
    line: Cow<'static, str>
}

#[derive(Debug)]
struct SearchBase {
    lines: HashMap<Cow<'static, str>, LineInfo>
}

#[derive(Debug)]
struct StringError {
    description: String,
    cause: Option<Box<Error>>
}

impl fmt::Display for StringError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

impl Error for StringError {
    fn description(&self) -> &str {
        self.description.as_ref()
    }

    fn cause(&self) -> Option<&Error> {
        match self.cause {
            None => None,
            Some(ref error) => Some(error.borrow())
        }
    }
}

impl StringError {
    fn new<T: Into<String>>(description: T, cause: Option<Box<Error>>) -> StringError {
        StringError {
            description: description.into(),
            cause: cause
        }
    }
}

impl Default for SearchBase {
    fn default() -> SearchBase {
        SearchBase {
            lines: HashMap::default()
        }
    }
}

impl SearchBase {
    pub fn read_history<T: AsRef<path::Path>>(&mut self, path: T) -> Result<isize, StringError> {
        let input_file = match File::open(path) {
            Ok(f) => BufReader::new(f),
            Err(e) => return Err(StringError::new("Could not open history file", Some(Box::new(e))))
        };

        let mut line_number = -1;

        for m_line in input_file.lines() {
            let line = match m_line {
                Ok(mut line) => {
                    if line.len() > MAX_LEN {
                        let mut cut_at = None;
                        for (idx, _) in line.char_indices() {
                            if idx > MAX_LEN {
                                cut_at = Some(idx);
                                break;
                            }
                        }
                        match cut_at {
                            None => {
                                // do nothing, last character spans the 80th byte
                            },
                            Some(idx) => {
                                line.truncate(idx);
                            }
                        }
                    }
                    // return the result
                    line
                },
                Err(e) => {
                    return Err(StringError::new("Failed to read line", Some(Box::new(e))));
                }
            };

            line_number += 1;

            // generate the line info
            let info = LineInfo::new(&line, line_number);

            // insert the line into the map
            self.lines.insert(line.into_cow(), info);
        }

        Ok(line_number)
    }

    pub fn query_inplace<T: AsRef<str>>(&self, query: T, matches: &mut BinaryHeap<LineMatch>) {
        // search for a match
        for (line, info) in self.lines.iter() {
            let line_score = match info.query_score(&query) {
                None => {
                    // non-matching line
                    continue;
                },
                Some(score) => {
                    score
                }
            };

            // negate everything so we can use push_pop
            let match_item = LineMatch {
                score: -line_score,
                factor: -info.factor,
                line: line.clone()
            };
            let matches_len = matches.len();
            let matches_capacity = matches.capacity();
            let insert;
            match matches.peek() {
                None => {
                    insert = true;
                },
                Some(item) => {
                    if &match_item < item || matches_len < matches_capacity {
                        insert = true
                    } else {
                        insert = false;
                    }
                }
            }
            if insert {
                if matches_len < matches_capacity {
                    matches.push(match_item);
                } else {
                    matches.push_pop(match_item);
                }
            }
        }
    }

    pub fn query<T: AsRef<str>>(&self, query: T) -> Vec<Cow<str>> {
        // allocate the match object
        let mut matches: BinaryHeap<LineMatch> = BinaryHeap::with_capacity(MATCH_NUMBER);

        self.query_inplace(query, &mut matches);

        // result contains a vector of the top MATCH_NUMBER lines, in descending score order
        matches.into_sorted_vec().into_iter().map(|x| {x.line}).collect()
    }
}

#[derive(PartialEq)]
enum CharClass {
    Whitespace,
    Numeric,
    Alphabetic,
    First,
    Other
}

impl Ord for LineMatch {
    fn cmp(&self, other: &LineMatch) -> cmp::Ordering {
        match self.score.cmp(&other.score) {
            cmp::Ordering::Equal => self.factor.cmp(&other.factor),
            order => order
        }
    }
}

impl PartialOrd for LineMatch {
    fn partial_cmp(&self, other: &LineMatch) -> Option<cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl PartialEq for LineMatch {
    fn eq(&self, other: &LineMatch) -> bool {
        self.cmp(other) == cmp::Ordering::Equal
    }
}

impl Eq for LineMatch {}

impl LineInfo {
    fn new<T: AsRef<str>>(item: T, factor: isize) -> LineInfo {
        let mut map: HashMap<char, Vec<usize>> = HashMap::new();
        let mut heat = vec![];
        let line = item.as_ref();

        let mut ws_score = 0;
        let mut cs_score = 0;
        let mut cur_class = CharClass::First;
        // character class changes don't stack
        let mut cs_change = false;

        for (idx, c) in line.chars().enumerate() {
            // don't map whitespace
            if !c.is_whitespace() {
                // update the character class change score if needed
                if cur_class == CharClass::First {
                    // add the first character factor on top of class change
                    cs_score += FIRST_FACTOR;
                }
                if c.is_numeric() {
                    if cur_class != CharClass::Numeric {
                        cur_class = CharClass::Numeric;
                        if !cs_change {
                            cs_score += CLASS_FACTOR;
                            cs_change = true;
                        }
                    } else {
                        cs_change = false;
                    }
                } else if c.is_alphabetic() {
                    if cur_class != CharClass::Alphabetic {
                        cur_class = CharClass::Alphabetic;
                        if !cs_change {
                            cs_score += CLASS_FACTOR;
                            cs_change = true;
                        }
                    } else {
                        cs_change = false;
                    }
                } else {
                    if cur_class != CharClass::Other {
                        cur_class = CharClass::Other;
                        if !cs_change {
                            cs_score += CLASS_FACTOR;
                            cs_change = true;
                        }
                    } else {
                        cs_change = false;
                    }
                }

                // add an entry in the character map
                map.entry(c).or_insert(Vec::new()).push(idx);
                if c.is_uppercase() {
                    for lc in c.to_lowercase() {
                        // also insert all lowercase equivalents of this character
                        // but not the other way around, so that typing something
                        // uppercase specifies to match uppercase
                        map.entry(lc).or_insert(Vec::new()).push(idx);
                    }
                }
            } else {
                // whitespace is treated differently
                cur_class = CharClass::Whitespace;
                ws_score = WHITESPACE_FACTOR;
            }

            // push to the heatmap
            heat.push(ws_score + cs_score);

            // reduce things
            ws_score /= WHITESPACE_REDUCE;
            if !cs_change {
                cs_score /= CLASS_REDUCE;
            }
        }

        LineInfo {
            char_map: map,
            heatmap: heat,
            factor: factor
        }
    }

    fn query_sequence<T: AsRef<str>>(&self, query_item: T) -> Option<Vec<Vec<usize>>> {
        let query = query_item.as_ref();
        let mut positions: Vec<Vec<usize>> = vec![];

        for c in query.chars() {
            match self.char_map.get(&c) {
                None => break,
                Some(list) => {
                    let to_push;
                    match positions.last() {
                        None => {
                            to_push = list.clone();
                        },
                        Some(item) => {
                            match list.binary_search(&item[0]) {
                                Ok(idx) => {
                                    if idx >= list.len() - 1 {
                                        // line is non-matching
                                        break;
                                    } else {
                                        to_push = list.split_at(idx + 1).1.into();
                                    }
                                },
                                Err(idx) => {
                                    if idx >= list.len() {
                                        // line is non-matching
                                        break;
                                    } else {
                                        to_push = list.split_at(idx).1.into();
                                    }
                                }
                            }
                        }
                    }
                    positions.push(to_push);
                }
            }
        }

        if positions.len() == query.len() {
            Some(positions)
        } else {
            None
        }
    }

    fn query_positions<T: AsRef<str>>(&self, query: T) -> Option<Vec<Vec<usize>>> {
        match self.query_sequence(query) {
            None => None,
            Some(positions) => {
                // matching line
                // create our idx vector
                let mut idx = vec![0; positions.len()];
                let mut result = vec![];
                loop {
                    // check that current configuration is strictly increasing
                    let mut ignore = false;
                    {
                        let mut last_pos = None;
                        for (i, pos) in idx.iter().enumerate() {
                            match last_pos {
                                None => last_pos = Some(positions[i][*pos]),
                                Some(other) => {
                                    if other >= positions[i][*pos] {
                                        ignore = true;
                                        break;
                                    } else {
                                        last_pos = Some(positions[i][*pos]);
                                    }
                                }
                            }
                        }
                    }

                    if !ignore {
                        // add the configuration to the list
                        result.push(idx.iter().enumerate().map(|(i, pos)| {positions[i][*pos]}).collect());
                    }

                    // update our position vector
                    let mut update_idx = idx.len() - 1;
                    let mut finished = false;
                    loop {
                        idx[update_idx] += 1;
                        if idx[update_idx] >= positions[update_idx].len() {
                            if update_idx == 0 {
                                // we're finished with all permutations
                                finished = true;
                                break;
                            } else {
                                idx[update_idx] = 0;
                                update_idx -= 1;
                            }
                        } else {
                            // finished updating for this permutation
                            break;
                        }
                    }
                    if finished {
                        // finished with everything
                        break;
                    }
                }

                // return result
                Some(result)
            }
        }
    }

    fn query_score<T: AsRef<str>>(&self, query: T) -> Option<isize> {
        match self.query_positions(query) {
            None => None,
            Some(positions) => {
                let mut top_score = None;
                for pgroup in positions.iter() {
                    // find the average distance between the indexes
                    let mut dist_total = 0;
                    let mut dist_count = 0;
                    for i in 0..pgroup.len() - 1 {
                        dist_total += (pgroup[i + 1] - pgroup[i]) as isize;
                        dist_count += 1;
                    }
                    // sum the heatmap
                    let heat_sum: isize = pgroup.iter().map(|pos| {self.heatmap[*pos]}).sum();
                    let score = (dist_total / dist_count) * DIST_WEIGHT +
                        heat_sum * HEAT_WEIGHT;
                    match top_score {
                        None => top_score = Some(score),
                        Some(last) => {
                            if score > last {
                                top_score = Some(score);
                            }
                        }
                    }
                }

                // return the result
                match top_score {
                    None => None,
                    Some(score) => {
                        Some(score + self.factor / FACTOR_REDUCE)
                    }
                }
            }
        }
    }
}

fn main() {
    let history_path = match env::var("HISTFILE") {
        Ok(p) => p,
        Err(e) => panic!("Failed to get bash history file: {}", e)
    };

    // create a hashmap of lines to info
    let mut base = SearchBase::default();

    // read the history
    println!("Reading history...");
    match base.read_history(history_path) {
        Ok(_) => {
            // success
        },
        Err(e) => {
            panic!("Failed to read history: {}", e)
        }
    }

    let mut query = String::new();

    print!("Match input: ");
    match io::stdout().flush() {
        Err(e) => panic!("Failed to flush stdout: {}", e),
        Ok(_) => {}
    }

    match io::stdin().read_line(&mut query) {
        Ok(_) => {},
        Err(e) => panic!("Failed to read input line: {}", e)
    }

    match query.pop() {
        Some('\n') => {/* pop off trailing newline */},
        Some(c) => query.push(c),
        None => {/* Do nothing with an empty query */}
    }

    let result = base.query(&query);

    println!("Matches:");

    for item in result.iter() {
        println!("{}", item);
    }
}
<|endoftext|>


<commit_before>mod google;
mod stackexchange;

extern crate regex;
extern crate hyper;
extern crate rustc_serialize;
extern crate select;
extern crate flate2;
extern crate getopts;

use std::env;
use std::process::exit;

use getopts::Options;

use rustc_serialize::json;

use google::Google;
use stackexchange::StackExchangeApi;
use stackexchange::StackExchangeAnswer;

#[derive(Debug)]
struct How2RSSettings {
    max_answers_count: usize,
    query: String,
    json: bool,
}

impl Default for How2RSSettings {
    fn default() -> Self {
        How2RSSettings {
            max_answers_count: 5,
            query: String::new(),
            json: false,
        }
    }
}

fn print_usage(opts: Options) {
    let brief = "Usage: how2-rs [options] query";
    print!("{}", opts.usage(&brief));
}

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

    let mut opts = Options::new();
    opts.optopt("m",
                "max-answers",
                "Maximum answers to retrieve (defaults to 5).",
                "COUNT");
    opts.optflag("j", "json", "Return answers json-encoded (defaults to false)");
    opts.optflag("h", "help", "Show usage.");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => {
            println!("{}", f.to_string());
            exit(1);
        }
    };

    if matches.opt_present("h") {
        print_usage(opts);
        exit(0);
    }

    let mut settings = How2RSSettings::default();

    if matches.opt_present("j") {
        settings.json = true;
    }

    if matches.opt_present("m") {
        let count_cli = matches.opt_str("m");
        match count_cli {
            Some(count_str) => {
                match count_str.parse::<usize>() {
                    Ok(count) => {
                        settings.max_answers_count = count;
                    }
                    Err(_) => {
                        println!("'m' option should be an integer.");
                        exit(1);
                    }
                }
            }
            None => {
                println!("Argument to option 'm' missing.");
                exit(1);
            }
        }
    }

    settings.query = matches.free.join(" ");

    settings
}

fn search(settings: &How2RSSettings) -> Vec<StackExchangeAnswer> {
    let google = Google::default();
    let links = google.google(&settings.query);
    let links_urls = links.iter()
                          .map(|l| l.link.as_str())
                          .collect();

    let api = StackExchangeApi::new();

    let mut answers = api.answers(links_urls);

    answers.sort_by_key(|a| -a.score);
    let left_answers: Vec<_> = answers.drain(..)
                                      .take(settings.max_answers_count)
                                      .collect();

    left_answers
}

fn main() {
    let settings = get_settings();
    let answers = search(&settings);

    if settings.json == true {
        let encoded = json::encode(&answers).unwrap();
        println!("{}", encoded);
    } else {
        for answer in answers.iter() {
            println!("{}", answer);
            println!("===-----===");
        }
    }
}

#[test]
fn stackexchange_works() {
    let mut settings = How2RSSettings::default();
    settings.query = String::from("rust lang linked list");

    let answers = search(settings);

    assert_eq!(answers.len(), 3);
    assert!(answers.iter().any(|a| a.answer_id == 22269167));
}

#[test]
fn no_bad_results() {
    let mut settings = How2RSSettings::default();
    settings.query = String::from("rust lang is not awesome");

    let answers = search(settings);

    assert_eq!(answers.len(), 0);
}
<commit_msg>Fixed typo.<commit_after>mod google;
mod stackexchange;

extern crate regex;
extern crate hyper;
extern crate rustc_serialize;
extern crate select;
extern crate flate2;
extern crate getopts;

use std::env;
use std::process::exit;

use getopts::Options;

use rustc_serialize::json;

use google::Google;
use stackexchange::StackExchangeApi;
use stackexchange::StackExchangeAnswer;

#[derive(Debug)]
struct How2RSSettings {
    max_answers_count: usize,
    query: String,
    json: bool,
}

impl Default for How2RSSettings {
    fn default() -> Self {
        How2RSSettings {
            max_answers_count: 5,
            query: String::new(),
            json: false,
        }
    }
}

fn print_usage(opts: Options) {
    let brief = "Usage: how2-rs [options] query";
    print!("{}", opts.usage(&brief));
}

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

    let mut opts = Options::new();
    opts.optopt("m",
                "max-answers",
                "Maximum answers to retrieve (defaults to 5).",
                "COUNT");
    opts.optflag("j", "json", "Return answers json-encoded (defaults to false).");
    opts.optflag("h", "help", "Show usage.");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => {
            println!("{}", f.to_string());
            exit(1);
        }
    };

    if matches.opt_present("h") {
        print_usage(opts);
        exit(0);
    }

    let mut settings = How2RSSettings::default();

    if matches.opt_present("j") {
        settings.json = true;
    }

    if matches.opt_present("m") {
        let count_cli = matches.opt_str("m");
        match count_cli {
            Some(count_str) => {
                match count_str.parse::<usize>() {
                    Ok(count) => {
                        settings.max_answers_count = count;
                    }
                    Err(_) => {
                        println!("'m' option should be an integer.");
                        exit(1);
                    }
                }
            }
            None => {
                println!("Argument to option 'm' missing.");
                exit(1);
            }
        }
    }

    settings.query = matches.free.join(" ");

    settings
}

fn search(settings: &How2RSSettings) -> Vec<StackExchangeAnswer> {
    let google = Google::default();
    let links = google.google(&settings.query);
    let links_urls = links.iter()
                          .map(|l| l.link.as_str())
                          .collect();

    let api = StackExchangeApi::new();

    let mut answers = api.answers(links_urls);

    answers.sort_by_key(|a| -a.score);
    let left_answers: Vec<_> = answers.drain(..)
                                      .take(settings.max_answers_count)
                                      .collect();

    left_answers
}

fn main() {
    let settings = get_settings();
    let answers = search(&settings);

    if settings.json == true {
        let encoded = json::encode(&answers).unwrap();
        println!("{}", encoded);
    } else {
        for answer in answers.iter() {
            println!("{}", answer);
            println!("===-----===");
        }
    }
}

#[test]
fn stackexchange_works() {
    let mut settings = How2RSSettings::default();
    settings.query = String::from("rust lang linked list");

    let answers = search(settings);

    assert_eq!(answers.len(), 3);
    assert!(answers.iter().any(|a| a.answer_id == 22269167));
}

#[test]
fn no_bad_results() {
    let mut settings = How2RSSettings::default();
    settings.query = String::from("rust lang is not awesome");

    let answers = search(settings);

    assert_eq!(answers.len(), 0);
}
<|endoftext|>


<commit_before>/// An example of a client/server agnostic WebSocket that takes input from stdin and sends that
/// input to all other peers.
///
/// For example, to create a network like this:
///
/// 3013 ---- 3012 ---- 3014
///   \        |
///    \       |
///     \      |
///      \     |
///       \    |
///        \   |
///         \  |
///          3015
///
/// Run these commands in separate processes
/// ./peer2peer
/// ./peer2peer --server localhost:3013 ws://localhost:3012
/// ./peer2peer --server localhost:3014 ws://localhost:3012
/// ./peer2peer --server localhost:3015 ws://localhost:3012 ws://localhost:3013
///
/// Stdin on 3012 will be sent to all other peers
/// Stdin on 3013 will be sent to 3012 and 3015
/// Stdin on 3014 will be sent to 3012 only
/// Stdin on 3015 will be sent to 3012 and 2013

extern crate ws;
extern crate url;
extern crate clap;
extern crate env_logger;
extern crate rustc_serialize;
extern crate bincode;
#[macro_use]
extern crate log;

pub mod handler;
pub mod message;

use message::PeerMessage;
use handler::MessageFactory;

use std::collections::hash_map::HashMap;
use std::clone::Clone;
use std::io;
use std::io::prelude::*;
use std::thread;
use std::sync::{Arc, Mutex};

use bincode::rustc_serialize::encode;

use clap::{App, Arg};

fn main() {
    // Setup logging
    env_logger::init().unwrap();

    // Parse command line arguments
    let matches = App::new("Simple Peer 2 Peer")
                      .version("1.0")
                      .author("Jonathan Almeida <hello@jonalmeida.com>")
                      .about("Connect to other peers and listen for incoming connections.")
                      .arg(Arg::with_name("server")
                               .short("s")
                               .long("server")
                               .value_name("SERVER")
                               .help("Set the address to listen for new connections. (default: \
                                      localhost:3012"))
                      .arg(Arg::with_name("PEER")
                               .help("A WebSocket URL to attempt to connect to at start.")
                               .multiple(true))
                      .arg(Arg::with_name("demo")
                               .short("d")
                               .long("demo")
                               .value_name("DEMO_SERVER")
                               .help("Sleeps for 4 seconds before receiving messages from this \
                                      peer."))
                      .get_matches();

    // Get address of this peer
    let my_addr = String::from(matches.value_of("server").unwrap_or("localhost:3012"));
    let demo_addr = matches.value_of("demo");
    let my_addr_clone = my_addr.clone();

    let mut clock = HashMap::new();
    clock.insert(my_addr.clone(), 0u32);
    let clock = Arc::new(Mutex::new(clock));

    // Creating a Factory for the WebSocket
    let mut factory = MessageFactory::build(clock.clone()).me(my_addr.clone().as_str());
    if let Some(demo) = demo_addr {
        factory.demo(demo);
    }

    // Create simple websocket that just prints out messages
    let mut me = ws::WebSocket::new(factory).unwrap();

    // Get a sender for ALL connections to the websocket
    let broacaster = me.broadcaster();

    // Setup thread for listening to stdin and sending messages to connections
    let input = thread::spawn(move || {
        let stdin = io::stdin();
        for line in stdin.lock().lines() {

            // Incrementing clock before sending
            if let Ok(mut clock) = clock.lock() {
                if let Some(clock) = clock.get_mut(&my_addr_clone) {
                    *clock += 1;
                }

                // Constructing new message
                let message = PeerMessage {
                    sender: my_addr_clone.clone(),
                    clocks: clock.clone(),
                    message: line.unwrap_or(String::from("n/a")),
                };

                // Encoding into bytes
                if let Ok(encoded_msg) = encode(&message, bincode::SizeLimit::Infinite) {
                    // Sending bytes to all connected clients
                    broacaster.send(&*encoded_msg.into_boxed_slice()).unwrap();
                }
            }
        }
    });

    // Connect to any existing peers specified on the cli
    if let Some(peers) = matches.values_of("PEER") {
        for peer in peers {
            if let Ok(peer) = url::Url::parse(peer) {
                me.connect(peer).unwrap();
            }
        }
    }

    // Run the websocket
    if let Err(e) = me.listen(my_addr.clone().as_str()) {
        panic!("Failed to start WebSocket.\n{}", e);
    }

    if let Err(e) = input.join() {
        panic!("Child thread paniced with non-normal reasons.Trace:\n{:?}", e);
    }
}
<commit_msg>Removed the join since we never reach there<commit_after>/// An example of a client/server agnostic WebSocket that takes input from stdin and sends that
/// input to all other peers.
///
/// For example, to create a network like this:
///
/// 3013 ---- 3012 ---- 3014
///   \        |
///    \       |
///     \      |
///      \     |
///       \    |
///        \   |
///         \  |
///          3015
///
/// Run these commands in separate processes
/// ./peer2peer
/// ./peer2peer --server localhost:3013 ws://localhost:3012
/// ./peer2peer --server localhost:3014 ws://localhost:3012
/// ./peer2peer --server localhost:3015 ws://localhost:3012 ws://localhost:3013
///
/// Stdin on 3012 will be sent to all other peers
/// Stdin on 3013 will be sent to 3012 and 3015
/// Stdin on 3014 will be sent to 3012 only
/// Stdin on 3015 will be sent to 3012 and 2013

extern crate ws;
extern crate url;
extern crate clap;
extern crate env_logger;
extern crate rustc_serialize;
extern crate bincode;
#[macro_use]
extern crate log;

pub mod handler;
pub mod message;

use message::PeerMessage;
use handler::MessageFactory;

use std::collections::hash_map::HashMap;
use std::clone::Clone;
use std::io;
use std::io::prelude::*;
use std::thread;
use std::sync::{Arc, Mutex};

use bincode::rustc_serialize::encode;

use clap::{App, Arg};

fn main() {
    // Setup logging
    env_logger::init().unwrap();

    // Parse command line arguments
    let matches = App::new("Simple Peer 2 Peer")
                      .version("1.0")
                      .author("Jonathan Almeida <hello@jonalmeida.com>")
                      .about("Connect to other peers and listen for incoming connections.")
                      .arg(Arg::with_name("server")
                               .short("s")
                               .long("server")
                               .value_name("SERVER")
                               .help("Set the address to listen for new connections. (default: \
                                      localhost:3012"))
                      .arg(Arg::with_name("PEER")
                               .help("A WebSocket URL to attempt to connect to at start.")
                               .multiple(true))
                      .arg(Arg::with_name("demo")
                               .short("d")
                               .long("demo")
                               .value_name("DEMO_SERVER")
                               .help("Sleeps for 4 seconds before receiving messages from this \
                                      peer."))
                      .get_matches();

    // Get address of this peer
    let my_addr = String::from(matches.value_of("server").unwrap_or("localhost:3012"));
    let demo_addr = matches.value_of("demo");
    let my_addr_clone = my_addr.clone();

    let mut clock = HashMap::new();
    clock.insert(my_addr.clone(), 0u32);
    let clock = Arc::new(Mutex::new(clock));

    // Creating a Factory for the WebSocket
    let mut factory = MessageFactory::build(clock.clone()).me(my_addr.clone().as_str());
    if let Some(demo) = demo_addr {
        factory.demo(demo);
    }

    // Create simple websocket that just prints out messages
    let mut me = ws::WebSocket::new(factory).unwrap();

    // Get a sender for ALL connections to the websocket
    let broacaster = me.broadcaster();

    // Setup thread for listening to stdin and sending messages to connections
    thread::spawn(move || {
        let stdin = io::stdin();
        for line in stdin.lock().lines() {

            // Incrementing clock before sending
            if let Ok(mut clock) = clock.lock() {
                if let Some(clock) = clock.get_mut(&my_addr_clone) {
                    *clock += 1;
                }

                // Constructing new message
                let message = PeerMessage {
                    sender: my_addr_clone.clone(),
                    clocks: clock.clone(),
                    message: line.unwrap_or(String::from("n/a")),
                };

                // Encoding into bytes
                if let Ok(encoded_msg) = encode(&message, bincode::SizeLimit::Infinite) {
                    // Sending bytes to all connected clients
                    broacaster.send(&*encoded_msg.into_boxed_slice()).unwrap();
                }
            }
        }
    });

    // Connect to any existing peers specified on the cli
    if let Some(peers) = matches.values_of("PEER") {
        for peer in peers {
            if let Ok(peer) = url::Url::parse(peer) {
                me.connect(peer).unwrap();
            }
        }
    }

    // Run the websocket
    if let Err(e) = me.listen(my_addr.clone().as_str()) {
        panic!("Failed to start WebSocket.\n{}", e);
    }
}
<|endoftext|>


<commit_before>extern crate hostname;
extern crate glob;
extern crate rust_shell as shell;

use std::io;
use std::io::Write;
use std::str;
use hostname::get_hostname;
use shell::state::ShellState;
use shell::circular_buffer::CircularBuffer;
use shell::envars::Envars;

fn main() {
    // TODO: Factor out more helper functions
    // TODO: Semicolons between commands on a single line
    // TODO: Pipes and output redirection
    // TODO: Syntax highlighting
    let mut shell = ShellState {
        variables: Envars::load(),
        history: CircularBuffer::new(10000),
    };

    // login only gives me $HOME, $SHELL, $PATH, $LOGNAME, and $MAIL
    shell.read_config();
    shell.variables.insert("USER", "ben");
    shell.variables.insert("HOSTNAME", get_hostname().unwrap().as_ref());

    shell.load_history();

    let mut input_buffer = String::new();
    loop {
        shell.prompt_read(&mut input_buffer);
        let mut args = input_buffer.split_whitespace();
        let cmd = args.next().unwrap_or("").to_owned();

        match cmd.as_ref() {
            "" => print!(""),
            "cd" => shell.cd(args),
            "echo" => shell.echo(args),
            "exit" => {
                io::stdout().flush().unwrap();
                shell.save_history();
                std::process::exit(0)
            },
            "history" => shell.print_history(),
            _ => shell.run_command(&cmd, args)
        };
    }
}
<commit_msg>adding defaults so we don't rely on a config file<commit_after>extern crate hostname;
extern crate glob;
extern crate rust_shell as shell;

use std::io;
use std::io::Write;
use std::str;
use hostname::get_hostname;
use shell::state::ShellState;
use shell::circular_buffer::CircularBuffer;
use shell::envars::Envars;

fn main() {
    // TODO: Factor out more helper functions
    // TODO: Semicolons between commands on a single line
    // TODO: Pipes and output redirection
    // TODO: Syntax highlighting
    let mut shell = ShellState {
        variables: Envars::load(),
        history: CircularBuffer::new(10000),
    };

    // login only gives me $HOME, $SHELL, $PATH, $LOGNAME, and $MAIL, so provide defaults here
    shell.variables.insert("PROMPT", "PROMPT={BOLD}{WHITE}╭{RED} ➜ {GREEN}{$USER}@{$HOSTNAME}:{CYAN}{$PWD}{WHITE}\n╰ ➤ ");
    shell.variables.insert("HISTSIZE", "1000");
    shell.variables.insert("USER", "ben");
    shell.variables.insert("HOSTNAME", get_hostname().unwrap().as_ref());

    shell.read_config();

    shell.load_history();

    let mut input_buffer = String::new();
    loop {
        shell.prompt_read(&mut input_buffer);
        let mut args = input_buffer.split_whitespace();
        let cmd = args.next().unwrap_or("").to_owned();

        match cmd.as_ref() {
            "" => print!(""),
            "cd" => shell.cd(args),
            "echo" => shell.echo(args),
            "exit" => {
                io::stdout().flush().unwrap();
                shell.save_history();
                std::process::exit(0)
            },
            "history" => shell.print_history(),
            _ => shell.run_command(&cmd, args)
        };
    }
}
<|endoftext|>


<commit_before>use clap::Parser;
use std::cmp::Ordering;
use std::fs::File;

fn normalize_col(col: &str) -> String {
    lazy_static::lazy_static! {
        static ref RE: regex::Regex = regex::Regex::new(r"\(.*?\)$").unwrap();
    }
    let mut col = RE
        .replace_all(col, "")
        .to_lowercase()
        .trim()
        .replace(['(', ')'], "")
        .replace([' ', '.', '-', '/'], "_")
        .replace('?', "")
        .replace([',', '&'], "_")
        .replace([':', '#'], "");
    if !col.chars().next().map(char::is_alphabetic).unwrap_or(true) {
        col = format!("c_{}", col)
    }
    col
}

fn _create_table(db: &mut rusqlite::Connection, table_name: &str, cols: &[String]) {
    let create_columns = cols
        .iter()
        .map(|c| format!("\"{}\" varchar", c))
        .collect::<Vec<String>>()
        .join(", ");
    db.execute(
        &format!("CREATE TABLE {} ({})", table_name, create_columns),
        &[] as &[&dyn rusqlite::types::ToSql],
    )
    .unwrap();
}

fn _load_table_from_path(
    db: &mut rusqlite::Connection,
    table_name: &str,
    path: &str,
    delimiter: u8,
) -> Result<Vec<String>, Box<dyn std::error::Error>> {
    let mut num_rows = 0;
    let f = File::open(path)?;
    let file_size = f.metadata().unwrap().len();
    let mut reader = csv::ReaderBuilder::new()
        .flexible(true)
        .delimiter(delimiter)
        .from_reader(f);

    let normalized_cols =
        reader
            .headers()?
            .iter()
            .map(normalize_col)
            .fold(vec![], |mut v, orig_col| {
                let mut col = orig_col.clone();
                let mut i = 1;
                while v.contains(&col) {
                    col = format!("{}_{}", orig_col, i);
                    i += 1
                }
                v.push(col);
                v
            });
    _create_table(db, table_name, &normalized_cols);

    let insert_query = format!(
        "INSERT INTO {} VALUES ({})",
        table_name,
        normalized_cols
            .iter()
            .map(|_| "?")
            .collect::<Vec<_>>()
            .join(", ")
    );
    let pb = indicatif::ProgressBar::new(file_size);
    pb.set_style(
        indicatif::ProgressStyle::default_bar()
            .template("[{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({eta})")
            .progress_chars("#>-"),
    );
    let mut records = reader.byte_records();
    let tx = db.transaction().unwrap();
    {
        let mut stmt = tx.prepare(&insert_query).expect("tx.prepare() failed");
        while let Some(row) = records.next() {
            let mut row = row?;
            match row.len().cmp(&normalized_cols.len()) {
                Ordering::Less => {
                    for _ in 0..normalized_cols.len() - row.len() {
                        row.push_field(b"");
                    }
                }
                Ordering::Greater => {
                    panic!("Too many fields on row {}, fields: {:?}", num_rows + 1, row);
                }
                Ordering::Equal => {}
            }
            stmt.execute(rusqlite::params_from_iter(
                row.iter().map(String::from_utf8_lossy),
            ))
            .unwrap();

            num_rows += 1;
            if num_rows % 10000 == 0 {
                pb.set_position(records.reader().position().byte())
            }
        }
    }
    tx.commit().unwrap();
    pb.finish();

    println!(
        "Loaded {} rows into {}({}) from {:?}",
        num_rows,
        table_name,
        normalized_cols.join(", "),
        path,
    );
    Ok(normalized_cols)
}

struct FromAnySqlType {
    value: String,
}

impl rusqlite::types::FromSql for FromAnySqlType {
    fn column_result(
        value: rusqlite::types::ValueRef<'_>,
    ) -> Result<FromAnySqlType, rusqlite::types::FromSqlError> {
        let result = match value {
            rusqlite::types::ValueRef::Null => "null".to_string(),
            rusqlite::types::ValueRef::Integer(v) => v.to_string(),
            rusqlite::types::ValueRef::Real(v) => v.to_string(),
            rusqlite::types::ValueRef::Blob(v) | rusqlite::types::ValueRef::Text(v) => {
                String::from_utf8(v.to_vec()).unwrap()
            }
        };
        Ok(FromAnySqlType { value: result })
    }
}

fn _prepare_query<'a>(
    conn: &'a mut rusqlite::Connection,
    query: &str,
) -> Result<rusqlite::Statement<'a>, String> {
    conn.prepare(query).map_err(|e| e.to_string())
}

fn _handle_query(conn: &mut rusqlite::Connection, line: &str) -> Result<(), String> {
    let mut stmt = _prepare_query(conn, line)?;
    let col_count = stmt.column_count();

    let mut table = comfy_table::Table::new();
    table.load_preset("││──╞═╪╡┆    ┬┴┌┐└┘");
    table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic);
    let mut title_row = comfy_table::Row::new();
    for col in stmt.column_names() {
        title_row.add_cell(comfy_table::Cell::new(col));
    }
    table.set_header(title_row);

    let mut results = stmt.query(&[] as &[&dyn rusqlite::types::ToSql]).unwrap();
    while let Ok(Some(r)) = results.next() {
        let mut row = comfy_table::Row::new();
        for i in 0..col_count {
            let cell: FromAnySqlType = r.get(i).unwrap();
            row.add_cell(comfy_table::Cell::new(&cell.value));
        }
        table.add_row(row);
    }
    println!("{}", table);
    Ok(())
}

fn _handle_export(conn: &mut rusqlite::Connection, line: &str) -> Result<(), String> {
    lazy_static::lazy_static! {
        static ref RE: regex::Regex = regex::Regex::new(r"^\.export\(([\w_\-\./]+)\) (.*)").unwrap();
    }
    let caps = RE
        .captures(line)
        .ok_or_else(|| "Must match `.export(file-name) SQL`".to_owned())?;
    let destination_path = &caps[1];
    let query = &caps[2];

    let mut stmt = _prepare_query(conn, query)?;
    let col_count = stmt.column_count();

    let mut writer = csv::Writer::from_path(destination_path).map_err(|e| format!("{:?}", e))?;
    writer.write_record(stmt.column_names()).unwrap();

    let mut results = stmt.query(&[] as &[&dyn rusqlite::types::ToSql]).unwrap();
    while let Ok(Some(r)) = results.next() {
        writer
            .write_record((0..col_count).map(|i| {
                let cell: FromAnySqlType = r.get(i).unwrap();
                cell.value
            }))
            .unwrap();
    }

    Ok(())
}

fn _process_query(conn: &mut rusqlite::Connection, line: &str) {
    let result = if line.starts_with(".export") {
        _handle_export(conn, line)
    } else if line.starts_with(".schema") {
        _handle_query(
            conn,
            "SELECT sql AS schema FROM sqlite_master WHERE name like 't%'",
        )
    } else {
        _handle_query(conn, line)
    };
    if let Err(e) = result {
        println!("{}", e);
    }
}

fn install_udfs(c: &mut rusqlite::Connection) -> Result<(), Box<dyn std::error::Error>> {
    c.create_scalar_function(
        "regexp_extract",
        3,
        rusqlite::functions::FunctionFlags::SQLITE_UTF8
            | rusqlite::functions::FunctionFlags::SQLITE_DETERMINISTIC,
        |ctx| {
            let re = ctx.get_or_create_aux(
                0,
                |vr| -> Result<_, Box<dyn std::error::Error + Send + Sync + 'static>> {
                    Ok(regex::Regex::new(vr.as_str()?)?)
                },
            )?;
            let value = ctx.get::<Box<str>>(1)?;
            let replacement = ctx.get::<Box<str>>(2)?;

            let caps = match re.captures(&value) {
                Some(caps) => caps,
                None => return Ok("".to_string()),
            };
            let mut dest = String::new();
            caps.expand(&replacement, &mut dest);
            Ok(dest)
        },
    )?;
    c.create_scalar_function(
        "regexp",
        2,
        rusqlite::functions::FunctionFlags::SQLITE_UTF8
            | rusqlite::functions::FunctionFlags::SQLITE_DETERMINISTIC,
        |ctx| {
            let re = ctx.get_or_create_aux(
                0,
                |vr| -> Result<_, Box<dyn std::error::Error + Send + Sync + 'static>> {
                    Ok(regex::Regex::new(vr.as_str()?)?)
                },
            )?;
            let value = ctx.get::<Box<str>>(1)?;
            Ok(re.is_match(&value))
        },
    )?;

    Ok(())
}

struct SimpleWordCompleter {
    words: Vec<String>,
}

static BREAK_CHARS: [u8; 5] = [b' ', b'(', b')', b',', b'.'];
impl SimpleWordCompleter {
    fn new(words: Vec<String>) -> SimpleWordCompleter {
        SimpleWordCompleter { words }
    }
}

impl rustyline::Helper for SimpleWordCompleter {}

impl rustyline::hint::Hinter for SimpleWordCompleter {
    type Hint = String;

    fn hint(&self, _line: &str, _pos: usize, _ctx: &rustyline::Context<'_>) -> Option<String> {
        None
    }
}

impl rustyline::highlight::Highlighter for SimpleWordCompleter {}

impl rustyline::validate::Validator for SimpleWordCompleter {}

impl rustyline::completion::Completer for SimpleWordCompleter {
    type Candidate = String;

    fn complete(
        &self,
        line: &str,
        pos: usize,
        _ctx: &rustyline::Context<'_>,
    ) -> rustyline::Result<(usize, Vec<String>)> {
        let (start, word) = rustyline::completion::extract_word(line, pos, None, &BREAK_CHARS);

        let matches = self
            .words
            .iter()
            .filter(|w| w.starts_with(word))
            .cloned()
            .collect();
        Ok((start, matches))
    }
}

#[derive(clap::Parser)]
struct Opts {
    #[clap(long, help = "Use ',' as the delimiter for the CSV")]
    comma: bool,
    #[clap(long, help = "Use '|' as the delimiter for the CSV")]
    pipe: bool,
    #[clap(long, help = "Use '\\t' as the delimiter for the CSV")]
    tab: bool,
    #[clap(long, help = "Use ';' as the delimiter for the CSV")]
    semicolon: bool,

    #[clap()]
    paths: Vec<String>,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let opts = Opts::parse();

    let delim = match (opts.comma, opts.pipe, opts.tab, opts.semicolon) {
        (true, false, false, false) | (false, false, false, false) => b',',
        (false, true, false, false) => b'|',
        (false, false, true, false) => b'\t',
        (false, false, false, true) => b';',
        _ => {
            eprintln!("Can't pass more than one of --comma, --pipe, and --tab");
            std::process::exit(1);
        }
    };

    let mut conn = rusqlite::Connection::open_in_memory().unwrap();

    let mut base_words = [
        // keywords
        "distinct",
        "select",
        "from",
        "group",
        "by",
        "order",
        "where",
        "count",
        "limit",
        "offset",
        // functions
        "length",
        "coalesce",
        "regexp_extract",
        // csv-sql commands
        "export",
        "schema",
    ]
    .iter()
    .map(|&s| s.to_string())
    .collect::<Vec<String>>();

    if opts.paths.len() == 1 {
        let mut col_names = _load_table_from_path(&mut conn, "t", &opts.paths[0], delim)?;
        base_words.append(&mut col_names);
    } else {
        for (idx, path) in opts.paths.iter().enumerate() {
            let mut col_names =
                _load_table_from_path(&mut conn, &format!("t{}", idx + 1), path, delim)?;
            base_words.append(&mut col_names);
        }
    }

    install_udfs(&mut conn)?;

    let completer = SimpleWordCompleter::new(base_words);
    let mut rl = rustyline::Editor::new();
    rl.set_helper(Some(completer));
    let history_path = dirs::home_dir().unwrap().join(".csv-sql-history");
    let _ = rl.load_history(&history_path);
    loop {
        match rl.readline("> ") {
            Ok(line) => {
                if line.trim().is_empty() {
                    continue;
                }
                _process_query(&mut conn, &line);
                rl.add_history_entry(line);
            }
            Err(rustyline::error::ReadlineError::Interrupted) => {
                println!("Interrupted");
                continue;
            }
            Err(rustyline::error::ReadlineError::Eof) => {
                break;
            }
            Err(err) => {
                println!("Error: {}", err);
                break;
            }
        }
    }
    rl.save_history(&history_path).unwrap();

    Ok(())
}

#[cfg(test)]
mod test {
    use super::normalize_col;

    #[test]
    fn test_normalize_col() {
        for (value, expected) in &[
            ("", ""),
            ("abc", "abc"),
            ("(S)AO", "sao"),
            ("abc (123)", "abc"),
            ("2/6/2000", "c_2_6_2000"),
            ("COMBO#", "combo"),
        ] {
            assert_eq!(&&normalize_col(value), expected);
        }
    }
}
<commit_msg>clippy<commit_after>use clap::Parser;
use std::cmp::Ordering;
use std::fs::File;

fn normalize_col(col: &str) -> String {
    lazy_static::lazy_static! {
        static ref RE: regex::Regex = regex::Regex::new(r"\(.*?\)$").unwrap();
    }
    let mut col = RE
        .replace_all(col, "")
        .to_lowercase()
        .trim()
        .replace(['(', ')'], "")
        .replace([' ', '.', '-', '/'], "_")
        .replace('?', "")
        .replace([',', '&'], "_")
        .replace([':', '#'], "");
    if !col.chars().next().map(char::is_alphabetic).unwrap_or(true) {
        col = format!("c_{col}")
    }
    col
}

fn _create_table(db: &mut rusqlite::Connection, table_name: &str, cols: &[String]) {
    let create_columns = cols
        .iter()
        .map(|c| format!("\"{c}\" varchar"))
        .collect::<Vec<String>>()
        .join(", ");
    db.execute(
        &format!("CREATE TABLE {table_name} ({create_columns})"),
        &[] as &[&dyn rusqlite::types::ToSql],
    )
    .unwrap();
}

fn _load_table_from_path(
    db: &mut rusqlite::Connection,
    table_name: &str,
    path: &str,
    delimiter: u8,
) -> Result<Vec<String>, Box<dyn std::error::Error>> {
    let mut num_rows = 0;
    let f = File::open(path)?;
    let file_size = f.metadata().unwrap().len();
    let mut reader = csv::ReaderBuilder::new()
        .flexible(true)
        .delimiter(delimiter)
        .from_reader(f);

    let normalized_cols =
        reader
            .headers()?
            .iter()
            .map(normalize_col)
            .fold(vec![], |mut v, orig_col| {
                let mut col = orig_col.clone();
                let mut i = 1;
                while v.contains(&col) {
                    col = format!("{orig_col}_{i}");
                    i += 1
                }
                v.push(col);
                v
            });
    _create_table(db, table_name, &normalized_cols);

    let insert_query = format!(
        "INSERT INTO {} VALUES ({})",
        table_name,
        normalized_cols
            .iter()
            .map(|_| "?")
            .collect::<Vec<_>>()
            .join(", ")
    );
    let pb = indicatif::ProgressBar::new(file_size);
    pb.set_style(
        indicatif::ProgressStyle::default_bar()
            .template("[{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({eta})")
            .progress_chars("#>-"),
    );
    let mut records = reader.byte_records();
    let tx = db.transaction().unwrap();
    {
        let mut stmt = tx.prepare(&insert_query).expect("tx.prepare() failed");
        while let Some(row) = records.next() {
            let mut row = row?;
            match row.len().cmp(&normalized_cols.len()) {
                Ordering::Less => {
                    for _ in 0..normalized_cols.len() - row.len() {
                        row.push_field(b"");
                    }
                }
                Ordering::Greater => {
                    panic!("Too many fields on row {}, fields: {:?}", num_rows + 1, row);
                }
                Ordering::Equal => {}
            }
            stmt.execute(rusqlite::params_from_iter(
                row.iter().map(String::from_utf8_lossy),
            ))
            .unwrap();

            num_rows += 1;
            if num_rows % 10000 == 0 {
                pb.set_position(records.reader().position().byte())
            }
        }
    }
    tx.commit().unwrap();
    pb.finish();

    println!(
        "Loaded {} rows into {}({}) from {:?}",
        num_rows,
        table_name,
        normalized_cols.join(", "),
        path,
    );
    Ok(normalized_cols)
}

struct FromAnySqlType {
    value: String,
}

impl rusqlite::types::FromSql for FromAnySqlType {
    fn column_result(
        value: rusqlite::types::ValueRef<'_>,
    ) -> Result<FromAnySqlType, rusqlite::types::FromSqlError> {
        let result = match value {
            rusqlite::types::ValueRef::Null => "null".to_string(),
            rusqlite::types::ValueRef::Integer(v) => v.to_string(),
            rusqlite::types::ValueRef::Real(v) => v.to_string(),
            rusqlite::types::ValueRef::Blob(v) | rusqlite::types::ValueRef::Text(v) => {
                String::from_utf8(v.to_vec()).unwrap()
            }
        };
        Ok(FromAnySqlType { value: result })
    }
}

fn _prepare_query<'a>(
    conn: &'a mut rusqlite::Connection,
    query: &str,
) -> Result<rusqlite::Statement<'a>, String> {
    conn.prepare(query).map_err(|e| e.to_string())
}

fn _handle_query(conn: &mut rusqlite::Connection, line: &str) -> Result<(), String> {
    let mut stmt = _prepare_query(conn, line)?;
    let col_count = stmt.column_count();

    let mut table = comfy_table::Table::new();
    table.load_preset("││──╞═╪╡┆    ┬┴┌┐└┘");
    table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic);
    let mut title_row = comfy_table::Row::new();
    for col in stmt.column_names() {
        title_row.add_cell(comfy_table::Cell::new(col));
    }
    table.set_header(title_row);

    let mut results = stmt.query(&[] as &[&dyn rusqlite::types::ToSql]).unwrap();
    while let Ok(Some(r)) = results.next() {
        let mut row = comfy_table::Row::new();
        for i in 0..col_count {
            let cell: FromAnySqlType = r.get(i).unwrap();
            row.add_cell(comfy_table::Cell::new(cell.value));
        }
        table.add_row(row);
    }
    println!("{table}");
    Ok(())
}

fn _handle_export(conn: &mut rusqlite::Connection, line: &str) -> Result<(), String> {
    lazy_static::lazy_static! {
        static ref RE: regex::Regex = regex::Regex::new(r"^\.export\(([\w_\-\./]+)\) (.*)").unwrap();
    }
    let caps = RE
        .captures(line)
        .ok_or_else(|| "Must match `.export(file-name) SQL`".to_owned())?;
    let destination_path = &caps[1];
    let query = &caps[2];

    let mut stmt = _prepare_query(conn, query)?;
    let col_count = stmt.column_count();

    let mut writer = csv::Writer::from_path(destination_path).map_err(|e| format!("{e:?}"))?;
    writer.write_record(stmt.column_names()).unwrap();

    let mut results = stmt.query(&[] as &[&dyn rusqlite::types::ToSql]).unwrap();
    while let Ok(Some(r)) = results.next() {
        writer
            .write_record((0..col_count).map(|i| {
                let cell: FromAnySqlType = r.get(i).unwrap();
                cell.value
            }))
            .unwrap();
    }

    Ok(())
}

fn _process_query(conn: &mut rusqlite::Connection, line: &str) {
    let result = if line.starts_with(".export") {
        _handle_export(conn, line)
    } else if line.starts_with(".schema") {
        _handle_query(
            conn,
            "SELECT sql AS schema FROM sqlite_master WHERE name like 't%'",
        )
    } else {
        _handle_query(conn, line)
    };
    if let Err(e) = result {
        println!("{e}");
    }
}

fn install_udfs(c: &mut rusqlite::Connection) -> Result<(), Box<dyn std::error::Error>> {
    c.create_scalar_function(
        "regexp_extract",
        3,
        rusqlite::functions::FunctionFlags::SQLITE_UTF8
            | rusqlite::functions::FunctionFlags::SQLITE_DETERMINISTIC,
        |ctx| {
            let re = ctx.get_or_create_aux(
                0,
                |vr| -> Result<_, Box<dyn std::error::Error + Send + Sync + 'static>> {
                    Ok(regex::Regex::new(vr.as_str()?)?)
                },
            )?;
            let value = ctx.get::<Box<str>>(1)?;
            let replacement = ctx.get::<Box<str>>(2)?;

            let caps = match re.captures(&value) {
                Some(caps) => caps,
                None => return Ok("".to_string()),
            };
            let mut dest = String::new();
            caps.expand(&replacement, &mut dest);
            Ok(dest)
        },
    )?;
    c.create_scalar_function(
        "regexp",
        2,
        rusqlite::functions::FunctionFlags::SQLITE_UTF8
            | rusqlite::functions::FunctionFlags::SQLITE_DETERMINISTIC,
        |ctx| {
            let re = ctx.get_or_create_aux(
                0,
                |vr| -> Result<_, Box<dyn std::error::Error + Send + Sync + 'static>> {
                    Ok(regex::Regex::new(vr.as_str()?)?)
                },
            )?;
            let value = ctx.get::<Box<str>>(1)?;
            Ok(re.is_match(&value))
        },
    )?;

    Ok(())
}

struct SimpleWordCompleter {
    words: Vec<String>,
}

static BREAK_CHARS: [u8; 5] = [b' ', b'(', b')', b',', b'.'];
impl SimpleWordCompleter {
    fn new(words: Vec<String>) -> SimpleWordCompleter {
        SimpleWordCompleter { words }
    }
}

impl rustyline::Helper for SimpleWordCompleter {}

impl rustyline::hint::Hinter for SimpleWordCompleter {
    type Hint = String;

    fn hint(&self, _line: &str, _pos: usize, _ctx: &rustyline::Context<'_>) -> Option<String> {
        None
    }
}

impl rustyline::highlight::Highlighter for SimpleWordCompleter {}

impl rustyline::validate::Validator for SimpleWordCompleter {}

impl rustyline::completion::Completer for SimpleWordCompleter {
    type Candidate = String;

    fn complete(
        &self,
        line: &str,
        pos: usize,
        _ctx: &rustyline::Context<'_>,
    ) -> rustyline::Result<(usize, Vec<String>)> {
        let (start, word) = rustyline::completion::extract_word(line, pos, None, &BREAK_CHARS);

        let matches = self
            .words
            .iter()
            .filter(|w| w.starts_with(word))
            .cloned()
            .collect();
        Ok((start, matches))
    }
}

#[derive(clap::Parser)]
struct Opts {
    #[clap(long, help = "Use ',' as the delimiter for the CSV")]
    comma: bool,
    #[clap(long, help = "Use '|' as the delimiter for the CSV")]
    pipe: bool,
    #[clap(long, help = "Use '\\t' as the delimiter for the CSV")]
    tab: bool,
    #[clap(long, help = "Use ';' as the delimiter for the CSV")]
    semicolon: bool,

    #[clap()]
    paths: Vec<String>,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let opts = Opts::parse();

    let delim = match (opts.comma, opts.pipe, opts.tab, opts.semicolon) {
        (true, false, false, false) | (false, false, false, false) => b',',
        (false, true, false, false) => b'|',
        (false, false, true, false) => b'\t',
        (false, false, false, true) => b';',
        _ => {
            eprintln!("Can't pass more than one of --comma, --pipe, and --tab");
            std::process::exit(1);
        }
    };

    let mut conn = rusqlite::Connection::open_in_memory().unwrap();

    let mut base_words = [
        // keywords
        "distinct",
        "select",
        "from",
        "group",
        "by",
        "order",
        "where",
        "count",
        "limit",
        "offset",
        // functions
        "length",
        "coalesce",
        "regexp_extract",
        // csv-sql commands
        "export",
        "schema",
    ]
    .iter()
    .map(|&s| s.to_string())
    .collect::<Vec<String>>();

    if opts.paths.len() == 1 {
        let mut col_names = _load_table_from_path(&mut conn, "t", &opts.paths[0], delim)?;
        base_words.append(&mut col_names);
    } else {
        for (idx, path) in opts.paths.iter().enumerate() {
            let mut col_names =
                _load_table_from_path(&mut conn, &format!("t{}", idx + 1), path, delim)?;
            base_words.append(&mut col_names);
        }
    }

    install_udfs(&mut conn)?;

    let completer = SimpleWordCompleter::new(base_words);
    let mut rl = rustyline::Editor::new();
    rl.set_helper(Some(completer));
    let history_path = dirs::home_dir().unwrap().join(".csv-sql-history");
    let _ = rl.load_history(&history_path);
    loop {
        match rl.readline("> ") {
            Ok(line) => {
                if line.trim().is_empty() {
                    continue;
                }
                _process_query(&mut conn, &line);
                rl.add_history_entry(line);
            }
            Err(rustyline::error::ReadlineError::Interrupted) => {
                println!("Interrupted");
                continue;
            }
            Err(rustyline::error::ReadlineError::Eof) => {
                break;
            }
            Err(err) => {
                println!("Error: {err}");
                break;
            }
        }
    }
    rl.save_history(&history_path).unwrap();

    Ok(())
}

#[cfg(test)]
mod test {
    use super::normalize_col;

    #[test]
    fn test_normalize_col() {
        for (value, expected) in &[
            ("", ""),
            ("abc", "abc"),
            ("(S)AO", "sao"),
            ("abc (123)", "abc"),
            ("2/6/2000", "c_2_6_2000"),
            ("COMBO#", "combo"),
        ] {
            assert_eq!(&&normalize_col(value), expected);
        }
    }
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate glium;

extern crate glium_sdl2;
extern crate sdl2;

use std::fmt;
use glium::{Surface, VertexBuffer, Program, index, uniforms};
use sdl2::event::Event;
use sdl2::keyboard::Keycode;
use glium_sdl2::{DisplayBuild, SDL2Facade};

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

impl fmt::Display for Vertex {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.position[0], self.position[1])
    }
}

fn handle_key(keycode: Keycode) -> bool {
    match keycode {
        Keycode::Escape => false,
        _ => true,
    }
}

fn load_file(name: &str) -> std::io::Result<String> {
    use std::io::prelude::*;
    use std::fs::File;
    let mut f = try!(File::open(name));
    let mut s = String::new();
    try!(f.read_to_string(&mut s));
    Result::Ok(s)
}

fn main() {
    let sdl_context = sdl2::init()
        .unwrap();
    let video_subsystem = sdl_context
        . video()
        .unwrap();
    let display = video_subsystem
        .window("rustroam", 1024, 576)
        .resizable()
        .build_glium()
        .unwrap();

    let v1 = Vertex { position: [-0.5, -0.5] };
    let v2 = Vertex { position: [ 0.0,  0.5] };
    let v3 = Vertex { position: [ 0.5, -0.25] };
    let shape = vec![v1, v2, v3];

    let vertex_buffer = VertexBuffer::new(&display, &shape)
        .unwrap();
    let indices = index::NoIndices(index::PrimitiveType::TrianglesList);

    fn create_shaders(display: &SDL2Facade) -> Program {
        let vertex_shader_src = load_file("shaders/basic.vert").unwrap();
        let fragment_shader_src = load_file("shaders/basic.frag").unwrap();
        Program::from_source(display, &vertex_shader_src, &fragment_shader_src, None).unwrap()
    }
    let program = create_shaders(&display);

    let mut running = true;
    let mut event_pump = sdl_context.event_pump().unwrap();

    while running {
        let mut target = display.draw();
        target.clear_color(0.05, 0.05, 0.05, 1.0);
        target.draw(&vertex_buffer,
                    &indices,
                    &program,
                    &uniforms::EmptyUniforms,
                    &Default::default())
            .unwrap();
        target
            .finish()
            .unwrap();

        for event in event_pump.poll_iter() {
            match event {
                Event::KeyDown { keycode, .. } => running = handle_key(keycode.unwrap()),
                Event::Quit { .. } => running = false,
                _ => ()
            }
        }
    }
}

#[cfg(test)]
mod test {
    use super::{Vertex, handle_key};
    use sdl2::keyboard::Keycode;

    #[test]
    fn test_vertex() {
        assert_eq!("(3, 5)", format!("{}", Vertex { position: [3.0, 5.0] }));
    }

    #[test]
    fn test_handle_key() {
        assert_eq!(false, handle_key(Keycode::Escape));
        assert_eq!(true, handle_key(Keycode::Space));
    }
}
<commit_msg>my own error type<commit_after>#[macro_use]
extern crate glium;

extern crate glium_sdl2;
extern crate sdl2;

use std::{fmt, io};
use glium::{Surface, VertexBuffer, Program, index, uniforms};
use sdl2::event::Event;
use sdl2::keyboard::Keycode;
use glium_sdl2::{DisplayBuild, SDL2Facade};

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

impl fmt::Display for Vertex {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.position[0], self.position[1])
    }
}

#[derive(Debug)]
enum Error {
    Io(io::Error),
    ProgramCreation(glium::program::ProgramCreationError)
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::Io(ref err) => err.fmt(f),
            Error::ProgramCreation(ref err) => err.fmt(f)
        }
    }
}

impl std::error::Error for Error {
    fn description(&self) -> &str {
        match *self {
            Error::Io(ref err) => err.description(),
            Error::ProgramCreation(ref err) => err.description()
        }
    }
}

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

impl From<glium::program::ProgramCreationError> for Error {
    fn from(err: glium::program::ProgramCreationError) -> Error {
        Error::ProgramCreation(err)
    }
}

type Result<T> = std::result::Result<T, Error>;

fn handle_key(keycode: Keycode) -> bool {
    match keycode {
        Keycode::Escape => false,
        _ => true,
    }
}

fn load_file(name: &str) -> Result<String> {
    use std::io::prelude::*;
    use std::fs::File;
    let (mut f, mut s) = (try!(File::open(name)), String::new());
    try!(f.read_to_string(&mut s));
    Ok(s)
}

fn main() {
    let sdl_context = sdl2::init()
        .unwrap();
    let video_subsystem = sdl_context
        . video()
        .unwrap();
    let display = video_subsystem
        .window("rustroam", 1024, 576)
        .resizable()
        .build_glium()
        .unwrap();

    let v1 = Vertex { position: [-0.5, -0.5] };
    let v2 = Vertex { position: [ 0.0,  0.5] };
    let v3 = Vertex { position: [ 0.5, -0.25] };
    let shape = vec![v1, v2, v3];

    let vertex_buffer = VertexBuffer::new(&display, &shape)
        .unwrap();
    let indices = index::NoIndices(index::PrimitiveType::TrianglesList);

    fn create_shaders(display: &SDL2Facade) -> Result<Program> {
        let vertex_shader_src = try!(load_file("shaders/basic.vert"));
        let fragment_shader_src = try!(load_file("shaders/basic.frag"));
        let program = try!(Program::from_source(display, &vertex_shader_src, &fragment_shader_src, None));
        Ok(program)
    }
    let program = create_shaders(&display).unwrap();

    let mut running = true;
    let mut event_pump = sdl_context.event_pump().unwrap();

    while running {
        let mut target = display.draw();
        target.clear_color(0.05, 0.05, 0.05, 1.0);
        target.draw(&vertex_buffer,
                    &indices,
                    &program,
                    &uniforms::EmptyUniforms,
                    &Default::default())
            .unwrap();
        target
            .finish()
            .unwrap();

        for event in event_pump.poll_iter() {
            match event {
                Event::KeyDown { keycode, .. } => running = handle_key(keycode.unwrap()),
                Event::Quit { .. } => running = false,
                _ => ()
            }
        }
    }
}

#[cfg(test)]
mod test {
    use super::{Vertex, handle_key};
    use sdl2::keyboard::Keycode;

    #[test]
    fn test_vertex() {
        assert_eq!("(3, 5)", format!("{}", Vertex { position: [3.0, 5.0] }));
    }

    #[test]
    fn test_handle_key() {
        assert_eq!(false, handle_key(Keycode::Escape));
        assert_eq!(true, handle_key(Keycode::Space));
    }
}
<|endoftext|>