<commit_before>extern crate mysql;
extern crate toml;
extern crate rustc_serialize;
#[macro_use]
extern crate lazy_static;

mod lib;

use mysql::conn::MyOpts;
use std::default::Default;
use mysql::conn::pool;
use mysql::value::from_value;

use lib::config;
use lib::downloader::DownloadDB;
use lib::downloader::Downloader;
use lib::DownloadError;
use lib::converter::Converter;

use std::fs::{remove_file};

static VERSION : &'static str = "0.1"; // String not valid
static SLEEP_MS: u32 = 5000;

lazy_static! {
    static ref CONFIG: config::Config = {
        println!("Starting yayd-backend v{}",&VERSION);
        config::init_config() //return
    };

}

//#[repr(i8)] broken, enum not usable as of #10292
// enum QueryCodes {
//     Waiting = 0,
//     InProgress = 1,
//     Finished = 2,
//     Failed = 3,
// }

fn main() {

    let pool = lib::db_connect(mysql_options(), SLEEP_MS);

    let converter = Converter::new(&CONFIG.general.ffmpeg_bin, &CONFIG.codecs.audio, pool.clone());
    let mut print_pause = true;
    loop {
        if let Some(result) = request_entry(& pool) {
            print_pause = true;
            if result.playlist {
                println!("Playlist not supported atm!");
                //TODO: set playlist entry to errg
            }
            let qid = result.qid.clone();                 //&QueryCodes::InProgress as i32
            lib::set_query_code(&mut pool.get_conn().unwrap(), &1, &result.qid).ok().expect("Failed to set query code!");
            lib::set_query_state(&pool.clone(),&qid, "started", false);
            let succes = match handle_download(result, None, &converter) {
                Ok(v) => v,
                Err(e) => {println!("Error: {:?}", e); false }
            };
            let code = if succes {
                2//QueryCodes::Finished as i32
            } else {
                3//QueryCodes::Failed as i32
            };
            lib::set_query_code(&mut pool.get_conn().unwrap(), &code,&qid).ok().expect("Failed to set query code!");
            let state = if code == 2 {
                "finished"
            } else {
                "failed"
            };
            lib::set_query_state(&pool.clone(),&qid, state, true);
        } else {
            if print_pause { println!("Pausing.."); print_pause = false; }
            std::thread::sleep_ms(SLEEP_MS);
        }
    }

    println!("EOL!");
}

///Download handler
///Used by the playlist/file handler to download one file
///Based on the quality it needs to download audio & video separately and convert them together
///In case of a playlist download it depends on the target download folder & if it should be bezipped
///In case of a DMCA we need to download the file via the socket connector,
///which will output a mp3, or if requested, the video but with the highest quality.
///Thus in case of a DMCA we can't pick a quality anymore.
///Also the filename depends on the socket output then.
///
///If it's a non-zipped single file, it's moved after a successful download, converted etc to the
///main folder from which it should be downloadable.
///The original non-ascii & url_encode'd name of the file is stored in the DB
fn handle_download(downl_db: DownloadDB, folder: Option<String>, converter: &Converter) -> Result<bool,DownloadError>{
    //update progress
    let is_zipped = match folder {
        Some(_) => true,
        None => false,
    };

    let dbcopy = downl_db.clone(); //copy, all implement copy & no &'s in use
    lib::update_steps(&dbcopy.pool.clone(),&dbcopy.qid, 1, 0);
    let download = Downloader::new(downl_db, &CONFIG.general);
    //get filename, check for DMCA
    let mut dmca = false; // "succ." dmca -> file already downloaded
    let name = match download.get_file_name() { // get filename
        Ok(v) => v,
        Err(DownloadError::DMCAError) => { //now request via lib.. // k if( k == Err(DownloadError::DMCAError) )
            println!("DMCA error!");
            match download.lib_request_video() {
                Err(err) => { println!("Offliberty-call error {:?}", err); return Err(err); },
                Ok(v) => { dmca = true; v },
            }
        },
        Err(e) => { // unknown error / video private etc.. abort
            println!("Unknown error: {:?}", e);
            return Err(e);
        },
    };

    let name_http_valid = lib::format_file_name(&name, &download, &dbcopy.qid);

    let file_path = format_file_path(&dbcopy.qid, folder.clone());
    let save_path = &format_save_path(folder.clone(),&name, &download, &dbcopy.qid);

    println!("Filename: {}", name);

    if !dmca {
        //TODO: insert title name for file,
        //copy file to download folder

        let is_splitted_format = lib::is_split_container(&dbcopy.quality);
        let total_steps = if is_splitted_format {
            4
        } else {
            2
        };
        lib::update_steps(&dbcopy.pool.clone(),&dbcopy.qid, 2, total_steps);

        //download video, which is video/audio(m4a)
        try!(download.download_file(&file_path, false));

        if is_splitted_format { // download audio file & convert together
            lib::update_steps(&dbcopy.pool.clone(),&dbcopy.qid, 3, total_steps);

            let audio_path = format_audio_path(&dbcopy.qid, folder.clone());

            println!("Downloading audio.. {}", audio_path);
            try!(download.download_file(&audio_path, true));

            lib::update_steps(&dbcopy.pool.clone(),&dbcopy.qid, 4, total_steps);

            match converter.merge_files(&dbcopy.qid,&file_path, &audio_path,&save_path) {
                Err(e) => {println!("merge error: {:?}",e); return Err(e);},
                Ok(()) => {},
            }

            try!(remove_file(&audio_path));

        }else{ // we're already done, only need to copy / convert to mp3 if requested
            if download.is_audio(){ // if audio-> convert m4a to mp3, which converts directly to downl. dir
                //TODO: convert -> saves already ?
            }else{
                try!(lib::move_file(&file_path, &save_path));
            }
        }
        try!(remove_file(&file_path));
    }else{
        try!(lib::move_file(&file_path, &save_path));
    }

    if !is_zipped { // add file to list, except it's for zip-compression later (=folder set)
        lib::add_file_entry(&dbcopy.pool.clone(), &dbcopy.qid,&name_http_valid, &name);
    }

    //TODO: download file, convert if audio ?!
    Ok(true)
}

///Format save location for file, zip dependent
fn format_file_path(qid: &i64, folder: Option<String>) -> String {
    match folder {
        Some(v) => format!("{}/{}/{}", &CONFIG.general.save_dir, v, qid),
        None => format!("{}/{}", &CONFIG.general.save_dir, qid),
    }
}

///Formats the audio path, based on the qid & optional folders
fn format_audio_path(qid: &i64, folder: Option<String>) -> String {
    match folder {
        Some(v) => format!("{}/{}/{}a", &CONFIG.general.save_dir, v, qid),
        None => format!("{}/{}a", &CONFIG.general.save_dir, qid),
    }
}

///Format save path, dependent on zip option.
fn format_save_path<'a>(folder: Option<String>, name: &str, download: &'a Downloader, qid: &i64) -> String {
    match folder {
        Some(v) => format!("{}/{}/{}", &CONFIG.general.save_dir, v, lib::format_file_name(name,download,qid)),
        None => format!("{}/{}", &CONFIG.general.download_dir, lib::format_file_name(name,download,qid)),
    }
}

///Request an entry from the DB to handle
fn request_entry(pool: & pool::MyPool) -> Option<DownloadDB> {
    let mut conn = pool.get_conn().unwrap();
    let mut stmt = conn.prepare("SELECT queries.qid,url,type,quality FROM querydetails \
                    INNER JOIN queries \
                    ON querydetails.qid = queries.qid \
                    WHERE querydetails.code = 0 \
                    ORDER BY queries.created \
                    LIMIT 1").unwrap();
    let mut result = stmt.execute(&[]).unwrap();
    let result = match result.next() {
        Some(val) => val.unwrap(),
        None => {return None; },
    };
    println!("Result: {:?}", result[0]);
    println!("result str: {}", result[1].into_str());
    let download_db = DownloadDB { url: from_value::<String>(&result[1]),
                                    quality: from_value::<i16>(&result[3]),
                                    qid: from_value::<i64>(&result[0]),
                                    audioquality: CONFIG.codecs.audio,
                                    folder: CONFIG.general.save_dir.clone(),
                                    pool: pool.clone(),
                                    playlist: false, //TEMP
                                    compress: false };
    Some(download_db)
}

///Set dbms connection settings
fn mysql_options() -> MyOpts {
    MyOpts {
        tcp_addr: Some(CONFIG.db.ip.clone()),
        tcp_port: CONFIG.db.port,
        user: Some(CONFIG.db.user.clone()),
        pass: Some(CONFIG.db.password.clone()),
        db_name: Some(CONFIG.db.db.clone()),
        ..Default::default() // set others to default
    }
}<commit_msg>+macro try_reoption *handling now sql request errors Closes #5<commit_after>extern crate mysql;
extern crate toml;
extern crate rustc_serialize;
#[macro_use]
extern crate lazy_static;

mod lib;

use mysql::conn::MyOpts;
use std::default::Default;
use mysql::conn::pool;
use mysql::value::from_value;

use lib::config;
use lib::downloader::DownloadDB;
use lib::downloader::Downloader;
use lib::DownloadError;
use lib::converter::Converter;

use std::fs::{remove_file};

//macro_rules! try_option { ($e:expr) => (match $e { Some(x) => x, None => return None }) } example

///Move result value out, return with none on err & print
macro_rules! try_reoption { ($e:expr) => (match $e { Ok(x) => x, Err(e) => {println!("{}",e);return None }}) }

static VERSION : &'static str = "0.1"; // String not valid
static SLEEP_MS: u32 = 5000;

lazy_static! {
    static ref CONFIG: config::Config = {
        println!("Starting yayd-backend v{}",&VERSION);
        config::init_config() //return
    };

}

//#[repr(i8)] broken, enum not usable as of #10292
// enum QueryCodes {
//     Waiting = 0,
//     InProgress = 1,
//     Finished = 2,
//     Failed = 3,
// }

fn main() {

    let pool = lib::db_connect(mysql_options(), SLEEP_MS);

    let converter = Converter::new(&CONFIG.general.ffmpeg_bin, &CONFIG.codecs.audio, pool.clone());
    let mut print_pause = true;
    loop {
        if let Some(result) = request_entry(& pool) {
            print_pause = true;
            if result.playlist {
                println!("Playlist not supported atm!");
                //TODO: set playlist entry to errg
            }
            let qid = result.qid.clone();                 //&QueryCodes::InProgress as i32
            lib::set_query_code(&mut pool.get_conn().unwrap(), &1, &result.qid).ok().expect("Failed to set query code!");
            lib::set_query_state(&pool.clone(),&qid, "started", false);
            let succes = match handle_download(result, None, &converter) {
                Ok(v) => v,
                Err(e) => {println!("Error: {:?}", e); false }
            };
            let code = if succes {
                2//QueryCodes::Finished as i32
            } else {
                3//QueryCodes::Failed as i32
            };
            lib::set_query_code(&mut pool.get_conn().unwrap(), &code,&qid).ok().expect("Failed to set query code!");
            let state = if code == 2 {
                "finished"
            } else {
                "failed"
            };
            lib::set_query_state(&pool.clone(),&qid, state, true);
        } else {
            if print_pause { println!("Pausing.."); print_pause = false; }
            std::thread::sleep_ms(SLEEP_MS);
        }
    }
}

///Download handler
///Used by the playlist/file handler to download one file
///Based on the quality it needs to download audio & video separately and convert them together
///In case of a playlist download it depends on the target download folder & if it should be bezipped
///In case of a DMCA we need to download the file via the socket connector,
///which will output a mp3, or if requested, the video but with the highest quality.
///Thus in case of a DMCA we can't pick a quality anymore.
///Also the filename depends on the socket output then.
///
///If it's a non-zipped single file, it's moved after a successful download, converted etc to the
///main folder from which it should be downloadable.
///The original non-ascii & url_encode'd name of the file is stored in the DB
fn handle_download(downl_db: DownloadDB, folder: Option<String>, converter: &Converter) -> Result<bool,DownloadError>{
    //update progress
    let is_zipped = match folder {
        Some(_) => true,
        None => false,
    };

    let dbcopy = downl_db.clone(); //copy, all implement copy & no &'s in use
    lib::update_steps(&dbcopy.pool.clone(),&dbcopy.qid, 1, 0);
    let download = Downloader::new(downl_db, &CONFIG.general);
    //get filename, check for DMCA
    let mut dmca = false; // "succ." dmca -> file already downloaded
    let name = match download.get_file_name() { // get filename
        Ok(v) => v,
        Err(DownloadError::DMCAError) => { //now request via lib.. // k if( k == Err(DownloadError::DMCAError) )
            println!("DMCA error!");
            match download.lib_request_video() {
                Err(err) => { println!("Offliberty-call error {:?}", err); return Err(err); },
                Ok(v) => { dmca = true; v },
            }
        },
        Err(e) => { // unknown error / video private etc.. abort
            println!("Unknown error: {:?}", e);
            return Err(e);
        },
    };

    let name_http_valid = lib::format_file_name(&name, &download, &dbcopy.qid);

    let file_path = format_file_path(&dbcopy.qid, folder.clone());
    let save_path = &format_save_path(folder.clone(),&name, &download, &dbcopy.qid);

    println!("Filename: {}", name);

    if !dmca {
        //TODO: insert title name for file,
        //copy file to download folder

        let is_splitted_format = lib::is_split_container(&dbcopy.quality);
        let total_steps = if is_splitted_format {
            4
        } else {
            2
        };
        lib::update_steps(&dbcopy.pool.clone(),&dbcopy.qid, 2, total_steps);

        //download video, which is video/audio(m4a)
        try!(download.download_file(&file_path, false));

        if is_splitted_format { // download audio file & convert together
            lib::update_steps(&dbcopy.pool.clone(),&dbcopy.qid, 3, total_steps);

            let audio_path = format_audio_path(&dbcopy.qid, folder.clone());

            println!("Downloading audio.. {}", audio_path);
            try!(download.download_file(&audio_path, true));

            lib::update_steps(&dbcopy.pool.clone(),&dbcopy.qid, 4, total_steps);

            match converter.merge_files(&dbcopy.qid,&file_path, &audio_path,&save_path) {
                Err(e) => {println!("merge error: {:?}",e); return Err(e);},
                Ok(()) => {},
            }

            try!(remove_file(&audio_path));

        }else{ // we're already done, only need to copy / convert to mp3 if requested
            if download.is_audio(){ // if audio-> convert m4a to mp3, which converts directly to downl. dir
                //TODO: convert -> saves already ?
            }else{
                try!(lib::move_file(&file_path, &save_path));
            }
        }
        try!(remove_file(&file_path));
    }else{
        try!(lib::move_file(&file_path, &save_path));
    }

    if !is_zipped { // add file to list, except it's for zip-compression later (=folder set)
        lib::add_file_entry(&dbcopy.pool.clone(), &dbcopy.qid,&name_http_valid, &name);
    }

    //TODO: download file, convert if audio ?!
    Ok(true)
}

///Format save location for file, zip dependent
fn format_file_path(qid: &i64, folder: Option<String>) -> String {
    match folder {
        Some(v) => format!("{}/{}/{}", &CONFIG.general.save_dir, v, qid),
        None => format!("{}/{}", &CONFIG.general.save_dir, qid),
    }
}

///Formats the audio path, based on the qid & optional folders
fn format_audio_path(qid: &i64, folder: Option<String>) -> String {
    match folder {
        Some(v) => format!("{}/{}/{}a", &CONFIG.general.save_dir, v, qid),
        None => format!("{}/{}a", &CONFIG.general.save_dir, qid),
    }
}

///Format save path, dependent on zip option.
fn format_save_path<'a>(folder: Option<String>, name: &str, download: &'a Downloader, qid: &i64) -> String {
    match folder {
        Some(v) => format!("{}/{}/{}", &CONFIG.general.save_dir, v, lib::format_file_name(name,download,qid)),
        None => format!("{}/{}", &CONFIG.general.download_dir, lib::format_file_name(name,download,qid)),
    }
}

///Request an entry from the DB to handle
fn request_entry(pool: & pool::MyPool) -> Option<DownloadDB> {
    let mut conn = try_reoption!(pool.get_conn());
    let mut stmt = try_reoption!(conn.prepare("SELECT queries.qid,url,type,quality FROM querydetails \
                    INNER JOIN queries \
                    ON querydetails.qid = queries.qid \
                    WHERE querydetails.code = 0 \
                    ORDER BY queries.created \
                    LIMIT 1"));
    let mut result = try_reoption!(stmt.execute(&[]));
    let result = match result.next() {
        Some(val) => try_reoption!(val),
        None => {return None; },
    };
    println!("Result: {:?}", result[0]);
    println!("result str: {}", result[1].into_str());
    let download_db = DownloadDB { url: from_value::<String>(&result[1]),
                                    quality: from_value::<i16>(&result[3]),
                                    qid: from_value::<i64>(&result[0]),
                                    audioquality: CONFIG.codecs.audio,
                                    folder: CONFIG.general.save_dir.clone(),
                                    pool: pool.clone(),
                                    playlist: false, //TEMP
                                    compress: false };
    Some(download_db)
}

///Set dbms connection settings
fn mysql_options() -> MyOpts {
    MyOpts {
        tcp_addr: Some(CONFIG.db.ip.clone()),
        tcp_port: CONFIG.db.port,
        user: Some(CONFIG.db.user.clone()),
        pass: Some(CONFIG.db.password.clone()),
        db_name: Some(CONFIG.db.db.clone()),
        ..Default::default() // set others to default
    }
}<|endoftext|>


<commit_before>#![allow(unstable, dead_code)]

extern crate libc;

mod args;
mod ansi;
mod matching;
mod screen;

use std::os;
use std::io;
use std::cmp::min;
use screen::Screen;
use screen::Key::*;

fn main() {
  let args = match args::parse_args() {
    Some(args) => args,
    None => {
      os::set_exit_status(1);
      return;
    },
  };

  if args.help { return; }

  let choices = read_choices();
  let initial_search = args.initial_search.clone();
  if args.use_first {
    let matches = matching::compute_matches(&choices, initial_search.as_slice());
    println!("{}", matches[0]);
    return;
  } else {
    event_loop(choices, initial_search.as_slice());
  }
}

fn event_loop(choices: Vec<String>, initial_search: &str) {
  let mut screen = Screen::open_screen();

  let mut search = Search {
    choices: &choices,
    query: initial_search.to_string(),
    matches: Vec::new(),
    stale: true,
    index: 0,
  };
  loop {
    search.recompute_matches();
    draw_screen(&mut screen, &search);

    let chars = screen.get_buffered_keys();
    for char in chars.iter() {
      match *char {
        Char(x) => search.append(x),
        Backspace => search.backspace(),
        Control('h') => search.backspace(),
        Control('u') => search.clear_query(),
        Control('c') => return,
        Control('n') => search.down(screen.visible_choices),
        Control('p') => search.up(),
        Enter => {
          screen.move_cursor_to_bottom();
          println!("{}", search.get_selection());
          return;
        }
        _ => panic!("Unexpected input"),
      }
    }
  }
}

struct Search<'a> {
  choices: &'a Vec<String>,
  query: String,
  matches: Vec<&'a String>,
  stale: bool,
  index: usize,
}

impl<'a> Search<'a> {
  fn up(&mut self) {
    self.index = if self.index == 0 { 0 } else { self.index - 1 };
  }

  fn down(&mut self, visible_choices: u16) {
    let limit = min(visible_choices as usize - 1, self.matches.len() - 1);
    self.index = min(self.index + 1, limit);
  }

  fn backspace(&mut self) {
    self.query.pop();
    self.stale = true;
    self.index = 0;
  }

  fn append(&mut self, c: char) {
    self.query.push(c);
    self.stale = true;
  }

  fn clear_query(&mut self) {
    self.query.clear();
    self.stale = true;
  }

  fn recompute_matches(&mut self) {
    if self.stale {
      self.matches = matching::compute_matches(self.choices, self.query.as_slice());
      self.stale = false;
    }
  }

  fn get_selection(&mut self) -> &String {
    self.recompute_matches();
    self.matches[self.index]
  }
}

fn draw_screen(screen: &mut Screen, search: &Search) {
  screen.hide_cursor();
  screen.blank_screen();
  screen.write(format!("> {} ({} choices)\n", search.query, search.matches.len()).as_slice());

  print_matches(screen, &search.matches, search.index);

  let start = screen.start_line;
  screen.move_cursor(start, 2 + search.query.len() as u16);
  screen.show_cursor();
}

fn print_matches(screen: &mut Screen, matches: &Vec<&String>, index: usize) {
  let mut i = 1;
  for choice in matches.iter() {
    if i == index + 1 {
      screen.write_inverted(choice.as_slice());
    } else {
      screen.write(choice.as_slice());
    }
    if i >= screen.visible_choices as usize {
      return;
    } else {
      screen.write("\n");
    }
    i += 1;
  }
}

fn read_choices() -> Vec<String> {
  let mut stdin = io::stdio::stdin();
  let mut lines = Vec::new();

  while let Ok(mut s) = stdin.read_line() {
    trim(&mut s);
    lines.push(s);
  }

  lines
}

fn trim(s: &mut String) {
  while let Some(x) = s.pop() {
    if x != '\n' {
      s.push(x);
      return;
    }
  }
}
<commit_msg>Extract a Search constructor<commit_after>#![allow(unstable, dead_code)]

extern crate libc;

mod args;
mod ansi;
mod matching;
mod screen;

use std::os;
use std::io;
use std::cmp::min;
use screen::Screen;
use screen::Key::*;

fn main() {
  let args = match args::parse_args() {
    Some(args) => args,
    None => {
      os::set_exit_status(1);
      return;
    },
  };

  if args.help { return; }

  let choices = read_choices();
  let initial_search = args.initial_search.clone();
  if args.use_first {
    let matches = matching::compute_matches(&choices, initial_search.as_slice());
    println!("{}", matches[0]);
    return;
  } else {
    event_loop(choices, initial_search.as_slice());
  }
}

fn event_loop(choices: Vec<String>, initial_search: &str) {
  let mut search = Search::new(&choices, initial_search.to_string());
  let mut screen = Screen::open_screen();

  loop {
    search.recompute_matches();
    draw_screen(&mut screen, &search);

    let chars = screen.get_buffered_keys();
    for char in chars.iter() {
      match *char {
        Char(x) => search.append(x),
        Backspace => search.backspace(),
        Control('h') => search.backspace(),
        Control('u') => search.clear_query(),
        Control('c') => return,
        Control('n') => search.down(screen.visible_choices),
        Control('p') => search.up(),
        Enter => {
          screen.move_cursor_to_bottom();
          println!("{}", search.get_selection());
          return;
        }
        _ => panic!("Unexpected input"),
      }
    }
  }
}

struct Search<'a> {
  choices: &'a Vec<String>,
  query: String,
  matches: Vec<&'a String>,
  stale: bool,
  index: usize,
}

impl<'a> Search<'a> {
  fn new(choices: &'a Vec<String>, initial_search: String) -> Search<'a> {
    Search {
      choices: choices,
      query: initial_search,
      matches: Vec::new(),
      stale: true,
      index: 0,
    }
  }

  fn up(&mut self) {
    self.index = if self.index == 0 { 0 } else { self.index - 1 };
  }

  fn down(&mut self, visible_choices: u16) {
    let limit = min(visible_choices as usize - 1, self.matches.len() - 1);
    self.index = min(self.index + 1, limit);
  }

  fn backspace(&mut self) {
    self.query.pop();
    self.stale = true;
    self.index = 0;
  }

  fn append(&mut self, c: char) {
    self.query.push(c);
    self.stale = true;
  }

  fn clear_query(&mut self) {
    self.query.clear();
    self.stale = true;
  }

  fn recompute_matches(&mut self) {
    if self.stale {
      self.matches = matching::compute_matches(self.choices, self.query.as_slice());
      self.stale = false;
    }
  }

  fn get_selection(&mut self) -> &String {
    self.recompute_matches();
    self.matches[self.index]
  }
}

fn draw_screen(screen: &mut Screen, search: &Search) {
  screen.hide_cursor();
  screen.blank_screen();
  screen.write(format!("> {} ({} choices)\n", search.query, search.matches.len()).as_slice());

  print_matches(screen, &search.matches, search.index);

  let start = screen.start_line;
  screen.move_cursor(start, 2 + search.query.len() as u16);
  screen.show_cursor();
}

fn print_matches(screen: &mut Screen, matches: &Vec<&String>, index: usize) {
  let mut i = 1;
  for choice in matches.iter() {
    if i == index + 1 {
      screen.write_inverted(choice.as_slice());
    } else {
      screen.write(choice.as_slice());
    }
    if i >= screen.visible_choices as usize {
      return;
    } else {
      screen.write("\n");
    }
    i += 1;
  }
}

fn read_choices() -> Vec<String> {
  let mut stdin = io::stdio::stdin();
  let mut lines = Vec::new();

  while let Ok(mut s) = stdin.read_line() {
    trim(&mut s);
    lines.push(s);
  }

  lines
}

fn trim(s: &mut String) {
  while let Some(x) = s.pop() {
    if x != '\n' {
      s.push(x);
      return;
    }
  }
}
<|endoftext|>


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

use std::collections::HashMap;
use std::io;
use std::io::prelude::*;
use std::fs;
use std::path::PathBuf;
use std::ffi::OsStr;
use std::env;
use std::env::consts::{DLL_EXTENSION, DLL_PREFIX};

extern crate itertools;
use itertools::*;

#[macro_use]
extern crate clap;
use clap::Arg;

extern crate colored;
use colored::*;

extern crate difference_engine;
use difference_engine::*;

fn main() {
  let arg_matches =
    clap::App::new("DEng, the Difference Engine")
      .version(crate_version!())
      .author("Alexander Altman")
      .about("An extensible language-aware diff")
      .settings(&[clap::AppSettings::ColoredHelp])
      .args(&[Arg::with_name("show languages")
                .long("show-languages")
                .short("L")
                .help("Lists all of the languages currently supported by DEng")
                .conflicts_with_all(&["show presentations", "language name", "presentation name", "old file", "new file"]),
              Arg::with_name("show presentations")
                .long("show-presentations")
                .short("P")
                .help("Lists all of the presentations currently supported by DEng")
                .conflicts_with_all(&["show languages", "language name", "presentation name", "old file", "new file"]),
              Arg::with_name("language name")
                .long("language")
                .short("l")
                .help("The language to use when examining the supplied files for differences.{n}If this option isn't provided, DEng \
                       will perform a naïve line-by-line diff.")
                .takes_value(true),
              Arg::with_name("presentation name")
                .long("presentation")
                .short("p")
                .help("The presentation to use when outputting the calculated diff.{n}If this option isn't provided, DEng will color \
                       new-file-only parts red and old-file-only parts green.")
                .takes_value(true),
              Arg::with_name("old file")
                .help("The file to consider “old” for the purposes of finding differences.{n}DEng will interpret a ‘-’ here as \
                       standard input.")
                .required(true),
              Arg::with_name("new file")
                .help("The file to consider “new” for the purposes of finding differences.{n}DEng will interpret a ‘-’ here as \
                       standard input.")
                .required(true)])
      .get_matches();

  let mut registered_languages_vec: Vec<Box<Language>> = vec![Box::new(SimpleLinewise), Box::new(SimpleCharwise)];
  let raw_language_plugin_path = env::var_os("DENG_RAW_LANGUAGE_PLUGIN_PATH");
  for plugin in env::current_dir()
    .into_iter()
    .map(|d| d.join(".deng").join("languages").join("raw"))
    .chain(raw_language_plugin_path.as_ref().into_iter().flat_map(env::split_paths))
    .chain(env::home_dir().into_iter().map(|d| d.join(".deng").join("languages").join("raw")))
    .flat_map(|d| d.read_dir().into_iter())
    .flatten()
    .flatten()
    .map(|e| e.path())
    .filter(|p| {
      p.file_stem().and_then(|s| s.to_str()).map(|s| s.starts_with(DLL_PREFIX)) == Some(true) &&
      p.extension().map(|s| s == DLL_EXTENSION) == Some(true)
    })
    .flat_map(|p| RawPluginLanguage::load(&p).into_iter()) {
    registered_languages_vec.push(Box::new(plugin));
  }
  let registered_languages: HashMap<String, Box<Language>> =
    registered_languages_vec.into_iter().map(|language| (language.name(), language)).unique_by(|nl| nl.0.clone()).collect();

  let mut registered_presentations_vec: Vec<Box<Presentation>> = vec![Box::new(BasicColored), Box::new(BasicStyled)];
  let raw_presentation_plugin_path = env::var_os("DENG_RAW_PRESENTATION_PLUGIN_PATH");
  for plugin in env::current_dir()
    .into_iter()
    .map(|d| d.join(".deng").join("presentations").join("raw"))
    .chain(raw_presentation_plugin_path.as_ref().into_iter().flat_map(env::split_paths))
    .chain(env::home_dir().into_iter().map(|d| d.join(".deng").join("presentations").join("raw")))
    .flat_map(|d| d.read_dir().into_iter())
    .flatten()
    .flatten()
    .map(|e| e.path())
    .filter(|p| {
      p.file_stem().and_then(|s| s.to_str()).map(|s| s.starts_with(DLL_PREFIX)) == Some(true) &&
      p.extension().map(|s| s == DLL_EXTENSION) == Some(true)
    })
    .flat_map(|p| RawPluginPresentation::load(&p).into_iter()) {
    registered_presentations_vec.push(Box::new(plugin));
  }
  let registered_presentations: HashMap<String, Box<Presentation>> =
    registered_presentations_vec.into_iter().map(|presentation| (presentation.name(), presentation)).unique_by(|np| np.0.clone()).collect();

  if arg_matches.is_present("show languages") {
    for language in registered_languages.values() {
      println!("{}:\t{}", language.name().bold(), language.description());
    }
    return;
  }

  if arg_matches.is_present("show presentations") {
    for presentation in registered_presentations.values() {
      println!("{}:\t{}", presentation.name().bold(), presentation.description());
    }
    return;
  }

  let language_name = arg_matches.value_of("language name").unwrap_or("simple-linewise");
  let language = registered_languages.get(language_name).expect("could not find language");

  let presentation_name = arg_matches.value_of("presentation name").unwrap_or("basic-colored");
  let presentation = registered_presentations.get(presentation_name).expect("could not find presentation");

  let (old_file, new_file) = resolve_files(arg_matches.value_of_os("old file").unwrap(),
                                           arg_matches.value_of_os("new file").unwrap());

  presentation.present(language.diff(old_file, new_file));
}

fn resolve_files(old_file_arg: &OsStr, new_file_arg: &OsStr) -> (String, String) {
  let old_file_path = handle_stdin_notation(old_file_arg).map(canonicalize_path);
  let new_file_path = handle_stdin_notation(new_file_arg).map(canonicalize_path);
  let old_file = old_file_path.clone()
                              .map(|path| read_whole_file(fs::File::open(path).expect("error opening file")))
                              .unwrap_or_else(|| read_whole_file(io::stdin()));
  let new_file;
  if old_file_path == new_file_path.clone() {
    new_file = old_file.clone();
  } else {
    new_file = new_file_path.map(|path| read_whole_file(fs::File::open(path).expect("error opening file")))
                            .unwrap_or_else(|| read_whole_file(io::stdin()));
  }
  return (old_file, new_file);
}

fn handle_stdin_notation(arg: &OsStr) -> Option<&OsStr> {
  if arg == "-" {
    return None;
  } else {
    return Some(arg);
  }
}

fn canonicalize_path(path: &OsStr) -> PathBuf { fs::canonicalize(path).expect("error locating file") }

fn read_whole_file<F: Read>(mut file: F) -> String {
  let mut result = String::new();
  file.read_to_string(&mut result).expect("error reading file");
  return result;
}
<commit_msg>Fix regression due to pending stabilization of Iterator::flatten (#1)<commit_after>// main.rs
// Copyright 2016 Alexander Altman
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::collections::HashMap;
use std::io;
use std::io::prelude::*;
use std::fs;
use std::path::PathBuf;
use std::ffi::OsStr;
use std::env;
use std::env::consts::{DLL_EXTENSION, DLL_PREFIX};

extern crate itertools;
use itertools::*;

#[macro_use]
extern crate clap;
use clap::Arg;

extern crate colored;
use colored::*;

extern crate difference_engine;
use difference_engine::*;

fn main() {
  let arg_matches =
    clap::App::new("DEng, the Difference Engine")
      .version(crate_version!())
      .author("Alexander Altman")
      .about("An extensible language-aware diff")
      .settings(&[clap::AppSettings::ColoredHelp])
      .args(&[Arg::with_name("show languages")
                .long("show-languages")
                .short("L")
                .help("Lists all of the languages currently supported by DEng")
                .conflicts_with_all(&["show presentations", "language name", "presentation name", "old file", "new file"]),
              Arg::with_name("show presentations")
                .long("show-presentations")
                .short("P")
                .help("Lists all of the presentations currently supported by DEng")
                .conflicts_with_all(&["show languages", "language name", "presentation name", "old file", "new file"]),
              Arg::with_name("language name")
                .long("language")
                .short("l")
                .help("The language to use when examining the supplied files for differences.{n}If this option isn't provided, DEng \
                       will perform a naïve line-by-line diff.")
                .takes_value(true),
              Arg::with_name("presentation name")
                .long("presentation")
                .short("p")
                .help("The presentation to use when outputting the calculated diff.{n}If this option isn't provided, DEng will color \
                       new-file-only parts red and old-file-only parts green.")
                .takes_value(true),
              Arg::with_name("old file")
                .help("The file to consider “old” for the purposes of finding differences.{n}DEng will interpret a ‘-’ here as \
                       standard input.")
                .required(true),
              Arg::with_name("new file")
                .help("The file to consider “new” for the purposes of finding differences.{n}DEng will interpret a ‘-’ here as \
                       standard input.")
                .required(true)])
      .get_matches();

  let mut registered_languages_vec: Vec<Box<Language>> = vec![Box::new(SimpleLinewise), Box::new(SimpleCharwise)];
  let raw_language_plugin_path = env::var_os("DENG_RAW_LANGUAGE_PLUGIN_PATH");
  for plugin in Itertools::flatten(Itertools::flatten(env::current_dir()
      .into_iter()
      .map(|d| d.join(".deng").join("languages").join("raw"))
      .chain(raw_language_plugin_path.as_ref().into_iter().flat_map(env::split_paths))
      .chain(env::home_dir().into_iter().map(|d| d.join(".deng").join("languages").join("raw")))
      .flat_map(|d| d.read_dir().into_iter())
    ))
    .map(|e| e.path())
    .filter(|p| {
      p.file_stem().and_then(|s| s.to_str()).map(|s| s.starts_with(DLL_PREFIX)) == Some(true) &&
      p.extension().map(|s| s == DLL_EXTENSION) == Some(true)
    })
    .flat_map(|p| RawPluginLanguage::load(&p).into_iter()) {
    registered_languages_vec.push(Box::new(plugin));
  }
  let registered_languages: HashMap<String, Box<Language>> =
    registered_languages_vec.into_iter().map(|language| (language.name(), language)).unique_by(|nl| nl.0.clone()).collect();

  let mut registered_presentations_vec: Vec<Box<Presentation>> = vec![Box::new(BasicColored), Box::new(BasicStyled)];
  let raw_presentation_plugin_path = env::var_os("DENG_RAW_PRESENTATION_PLUGIN_PATH");
  for plugin in env::current_dir()
    .into_iter()
    .map(|d| d.join(".deng").join("presentations").join("raw"))
    .chain(raw_presentation_plugin_path.as_ref().into_iter().flat_map(env::split_paths))
    .chain(env::home_dir().into_iter().map(|d| d.join(".deng").join("presentations").join("raw")))
    .flat_map(|d| d.read_dir().into_iter())
    .flatten()
    .flatten()
    .map(|e| e.path())
    .filter(|p| {
      p.file_stem().and_then(|s| s.to_str()).map(|s| s.starts_with(DLL_PREFIX)) == Some(true) &&
      p.extension().map(|s| s == DLL_EXTENSION) == Some(true)
    })
    .flat_map(|p| RawPluginPresentation::load(&p).into_iter()) {
    registered_presentations_vec.push(Box::new(plugin));
  }
  let registered_presentations: HashMap<String, Box<Presentation>> =
    registered_presentations_vec.into_iter().map(|presentation| (presentation.name(), presentation)).unique_by(|np| np.0.clone()).collect();

  if arg_matches.is_present("show languages") {
    for language in registered_languages.values() {
      println!("{}:\t{}", language.name().bold(), language.description());
    }
    return;
  }

  if arg_matches.is_present("show presentations") {
    for presentation in registered_presentations.values() {
      println!("{}:\t{}", presentation.name().bold(), presentation.description());
    }
    return;
  }

  let language_name = arg_matches.value_of("language name").unwrap_or("simple-linewise");
  let language = registered_languages.get(language_name).expect("could not find language");

  let presentation_name = arg_matches.value_of("presentation name").unwrap_or("basic-colored");
  let presentation = registered_presentations.get(presentation_name).expect("could not find presentation");

  let (old_file, new_file) = resolve_files(arg_matches.value_of_os("old file").unwrap(),
                                           arg_matches.value_of_os("new file").unwrap());

  presentation.present(language.diff(old_file, new_file));
}

fn resolve_files(old_file_arg: &OsStr, new_file_arg: &OsStr) -> (String, String) {
  let old_file_path = handle_stdin_notation(old_file_arg).map(canonicalize_path);
  let new_file_path = handle_stdin_notation(new_file_arg).map(canonicalize_path);
  let old_file = old_file_path.clone()
                              .map(|path| read_whole_file(fs::File::open(path).expect("error opening file")))
                              .unwrap_or_else(|| read_whole_file(io::stdin()));
  let new_file;
  if old_file_path == new_file_path.clone() {
    new_file = old_file.clone();
  } else {
    new_file = new_file_path.map(|path| read_whole_file(fs::File::open(path).expect("error opening file")))
                            .unwrap_or_else(|| read_whole_file(io::stdin()));
  }
  return (old_file, new_file);
}

fn handle_stdin_notation(arg: &OsStr) -> Option<&OsStr> {
  if arg == "-" {
    return None;
  } else {
    return Some(arg);
  }
}

fn canonicalize_path(path: &OsStr) -> PathBuf { fs::canonicalize(path).expect("error locating file") }

fn read_whole_file<F: Read>(mut file: F) -> String {
  let mut result = String::new();
  file.read_to_string(&mut result).expect("error reading file");
  return result;
}
<|endoftext|>


<commit_before>// By Emil Ernerfeldt December 2016
// LICENSE:
//   This software is dual-licensed to the public domain and under the following
//   license: you are granted a perpetual, irrevocable license to copy, modify,
//   publish, and distribute this file as you see fit.

use std::cmp::Ordering;
use std::fs::File;
use std::io::Write;
use std::path::Path;

extern crate rand;
use rand::{Rng, SeedableRng, StdRng};

extern crate pbr;
use pbr::ProgressBar;

extern crate quickersort;
extern crate time;

// ----------------------------------------------------------------------------

/// Sorts the elements using the given compare function.
/// Expected number of comparisons is O(N + K * log(K)) where K is the number of elements not in order.
/// Expected memory usage is O(K).
/// Works best for when K < 0.2 * N.
/// The out-of-order elements are expected to be randomly distributed (NOT clumped).
fn drop_merge_sort_by<T, F>(list: &mut [T], mut compare: F)
	where T: Copy,
		  F: FnMut(&T, &T) -> Ordering
{
	if list.len() < 2 { return; }

	// ------------------------------------------------------------------------
	// First step: heuristically find the Longest Nondecreasing Subsequence (LNS).
	// This is done using the memory-method described in
	// "Item Retention Improvements to Dropsort, a Lossy Sorting Algorithm"
	// by Abram Jackson and Ryan McCulloch
	// (http://micsymposium.org/mics_2011_proceedings/mics2011_submission_13.pdf)
	// The LNS is shifted into list[..write] while list[write..] will be left unchanged.
	// Elements not part of the LNS will be put in the "dropped" vector.

	let mut dropped = Vec::new();
	let mut num_dropped_in_row = 0;
	let mut write = 0; // Index of where to write the next element to keep.
	let mut read  = 0; // Index of the input stream.
	// Invariant: write <= read

	/// This speeds up well-ordered input by quite a lot.
	const DOUBLE_COMPARISONS : bool = true;

	/// Low RECENCY = faster when there is low randomness (a lot of order).
	/// High RECENCY = more resilient against long stretches of noise.
	/// If RECENCY is too small we are more dependent on nice data/luck.
	const RECENCY : usize = 8;

	while read < list.len() {
		if 1 <= write && compare(&list[read], &list[write - 1]) == Ordering::Less {
			// The next element is smaller than the last stored one.
			// The question is - should we drop the new element, or was accepting the previous element a mistake?

			/*
			   Check this situation:
			   0 1 2 3 9 5 6 7  (the 9 is a one-off)
			           | |
			           | read
			           write - 1
				Checking this improves performance because we catch common problems earlier (without back-tracking).
			*/
			if DOUBLE_COMPARISONS && num_dropped_in_row == 0 &&
			   2 <= write && compare(&list[read], &list[write - 2]) != Ordering::Less
			{
				// Quick undo: drop previously accepted element, and overwrite with new one:
				dropped.push(list[write - 1]);
				list[write - 1] = list[read];
				read += 1;
				continue;
			}

			if num_dropped_in_row < RECENCY {
				dropped.push(list[read]);
				read += 1;
				num_dropped_in_row += 1;
			} else {
				/*
				We accepted something num_dropped_in_row elements back that made us drop all RECENCY subsequent items.
				Accepting that element was obviously a mistake - so let's undo it!

				Example problem (RECENCY = 3):    0 1 12 3 4 5 6
					0 1 12 is accepted. 3, 4, 5 will be rejected because they are larger than the last kept item (12).
					When we get to 5 we reach num_dropped_in_row == RECENCY.
					This will trigger an undo where we drop the 12.
					When we again go to 3, we will keep it because it is larger than the last kept item (1).

				Example worst-case (RECENCY = 3):   ...100 101 102 103 104 1 2 3 4 5 ....
					100-104 is accepted. When we get to 3 we reach num_dropped_in_row == RECENCY.
					We drop 104 and reset the read by RECENCY. We restart, and then we drop again.
					This can lead us to backtracking RECENCY number of elements
					as many times as the leading non-decreasing subsequence is long.
				*/

				// Back up and recheck the elements we previously dropped:
				let trunc_to_length = dropped.len() - num_dropped_in_row;
				dropped.truncate(trunc_to_length);
				read -= num_dropped_in_row;

				// Drop the element we mistakingly accepted:
				dropped.push(list[write - 1]);
				write -= 1; // Over-write the dropped element.

				num_dropped_in_row = 0;
			}
		} else {
			// Keep:
			list[write] = list[read];
			read += 1;
			write += 1;
			num_dropped_in_row = 0;
		}
	}

	// ------------------------------------------------------------------------

	dropped.sort_by(|a, b| return compare(a, b));

	// ------------------------------------------------------------------------
	// list[..write] is now sorted, as is "dropped".
	// We now want to merge these into "list".
	// Let us do that from the back, putting the largest elements in place first:

	let mut back = list.len();

	while let Some(&last_dropped) = dropped.last() {
		while 0 < write && compare(&last_dropped, &list[write - 1]) == Ordering::Less {
			list[back - 1] = list[write - 1];
			back -= 1;
			write -= 1;
		}
		list[back - 1] = last_dropped;
		back -= 1;
		dropped.pop();
	}
}

fn drop_merge_sort<T>(list: &mut [T]) where T: Copy + Ord
{
	drop_merge_sort_by(list, |a, b| a.cmp(b))
}

// ----------------------------------------------------------------------------

type Element = i32;

/// Returns a mostly-sorted array with randomization_factor fraction of elements with random values.
fn generate_test_data(rng: &mut rand::StdRng, length: usize, randomization_factor: f32) -> Vec<Element> {
	let mut result = Vec::with_capacity(length);
	for i in 0..length {
		if rng.next_f32() < randomization_factor {
			result.push(rng.gen_range(0 as Element, length as Element));
		} else {
			result.push(i as Element);
		}
	}
	result
}

fn time_sort_ms<Sorter>(unsorted: &Vec<Element>, mut sorter: Sorter) -> (f32, Vec<Element>)
	where Sorter: FnMut(&mut Vec<Element>)
{
	let mut best_ns = None;
	let mut sorted = Vec::new();

	// Return fastest of five:
	for _ in 0..5 {
		let mut vec_clone = unsorted.clone();
		let start_time_ns = time::precise_time_ns();
		sorter(&mut vec_clone);
		let duration_ns = time::precise_time_ns() - start_time_ns;
		if best_ns == None || duration_ns < best_ns.unwrap() {
			best_ns = Some(duration_ns);
		}
		sorted = vec_clone;
	}

	(best_ns.unwrap() as f32 / 1000000.0, sorted)
}

fn generate_comparison_data(rng: &mut rand::StdRng, length: usize) {
	let mut pb = ProgressBar::new(1000);
	pb.message("Benchmarking against std::sort and quickersort: ");
	let mut std_file     = File::create(&Path::new("data/std_sort.data")).unwrap();
	let mut quicker_file = File::create(&Path::new("data/quicker_sort.data")).unwrap();
	let mut drop_file    = File::create(&Path::new("data/drop_merge_sort.data")).unwrap();

	for i in 0i32..1001i32 {
		let randomization_factor = (i as f32) / 1000.0;
		let vec = generate_test_data(rng, length, randomization_factor);
		let (std_duration_ms,     std_sorted)     = time_sort_ms(&vec, |x| x.sort());
		let (quicker_duration_ms, quicker_sorted) = time_sort_ms(&vec, |x| quickersort::sort(x));
		let (drop_duration_ms,    drop_sorted)    = time_sort_ms(&vec, |x| drop_merge_sort(x));

		assert_eq!(std_sorted, drop_sorted);
		assert_eq!(quicker_sorted, drop_sorted);

		write!(std_file,     "{} {}\n", randomization_factor * 100.0, std_duration_ms).unwrap();
		write!(quicker_file, "{} {}\n", randomization_factor * 100.0, quicker_duration_ms).unwrap();
		write!(drop_file,    "{} {}\n", randomization_factor * 100.0, drop_duration_ms).unwrap();
		pb.inc();
	}
}

fn generate_speedup_data(rng: &mut rand::StdRng, length: usize) {
	let mut pb = ProgressBar::new(250);
	pb.message("Benchmarking speedup over quickersort: ");
	let mut file = File::create(&Path::new("data/speedup_over_quickersort.data")).unwrap();

	for i in 0i32..251i32 {
		let randomization_factor = (i as f32) / 1000.0;
		let vec = generate_test_data(rng, length, randomization_factor);
		let (quicker_duration_ms, quicker_sorted) = time_sort_ms(&vec, |x| quickersort::sort(x));
		let (drop_duration_ms,    drop_sorted)    = time_sort_ms(&vec, |x| drop_merge_sort(x));

		assert_eq!(quicker_sorted, drop_sorted);

		write!(file, "{} {}\n", randomization_factor * 100.0, quicker_duration_ms / drop_duration_ms).unwrap();
		pb.inc();
	}
}

#[test]
fn run_tests() {
	fn test(mut list: Vec<Element>) {
		let mut std_sorted = list.clone();
		std_sorted.sort();

		println!();
		println!("IN:  {:?}", list);
		drop_merge_sort(&mut list);
		println!("OUT: {:?}", list);

		if list != std_sorted {
			panic!("FAIL with input {:?}", list);
		}
	}

	test(vec!());
	test(vec!(0));
	test(vec!(0, 1));
	test(vec!(1, 0));
	test(vec!(0, 1, 2));
	test(vec!(0, 2, 1));
	test(vec!(1, 0, 2));
	test(vec!(1, 2, 0));
	test(vec!(2, 0, 1));
	test(vec!(2, 1, 0));
	test(vec!(0, 1, 3, 2, 4, -5, 6, 7, 8, 9));
	test(vec!(0, 1, 10, 3, 4, 5, 6, 7, 8, 9));
	test(vec!(10, 1, 2, 3, 4, 5, 6, 7, 8, 9));
	test(vec!(0, 0, 2, 3, 4, 1, 6, 1, 8, 9));
	test(vec!(20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19));
	test(vec!(20, 21, 2, 23, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19));
}

/// Benchmark worst-case input for Drop-Merge sort
fn bench_evil() {
	let evil_input : Vec<_> = (100..1000000).chain(0..100).collect();
	let (std_duration_ms, std_sorted)         = time_sort_ms(&evil_input, |x| x.sort());
	let (quicker_duration_ms, quicker_sorted) = time_sort_ms(&evil_input, |x| quickersort::sort(x));
	let (drop_duration_ms,    drop_sorted)    = time_sort_ms(&evil_input, |x| drop_merge_sort(x));

	assert_eq!(std_sorted, drop_sorted);
	assert_eq!(quicker_sorted, drop_sorted);
	println!("Worst-case input:");
	println!("std::sort:       {} ms", std_duration_ms);
	println!("Quicksort:       {} ms", quicker_duration_ms);
	println!("Drop-Merge sort: {} ms", drop_duration_ms);
}

fn main() {
	bench_evil();
	let seed: &[_] = &[0];
	let mut rng: StdRng = SeedableRng::from_seed(seed);
	generate_speedup_data(&mut rng, 1000000); // ~1 min runtime
	generate_comparison_data(&mut rng, 1000000); // ~15 min runtime
}
<commit_msg>Cleanup benchmark code<commit_after>// By Emil Ernerfeldt December 2016
// LICENSE:
//   This software is dual-licensed to the public domain and under the following
//   license: you are granted a perpetual, irrevocable license to copy, modify,
//   publish, and distribute this file as you see fit.

use std::cmp::Ordering;
use std::fs::File;
use std::io::Write;
use std::path::Path;

extern crate rand;
use rand::{Rng, SeedableRng, StdRng};

extern crate pbr;
use pbr::ProgressBar;

extern crate quickersort;
extern crate time;

// ----------------------------------------------------------------------------

/// Sorts the elements using the given compare function.
/// Expected number of comparisons is O(N + K * log(K)) where K is the number of elements not in order.
/// Expected memory usage is O(K).
/// Works best for when K < 0.2 * N.
/// The out-of-order elements are expected to be randomly distributed (NOT clumped).
fn drop_merge_sort_by<T, F>(list: &mut [T], mut compare: F)
	where T: Copy,
		  F: FnMut(&T, &T) -> Ordering
{
	if list.len() < 2 { return; }

	// ------------------------------------------------------------------------
	// First step: heuristically find the Longest Nondecreasing Subsequence (LNS).
	// This is done using the memory-method described in
	// "Item Retention Improvements to Dropsort, a Lossy Sorting Algorithm"
	// by Abram Jackson and Ryan McCulloch
	// (http://micsymposium.org/mics_2011_proceedings/mics2011_submission_13.pdf)
	// The LNS is shifted into list[..write] while list[write..] will be left unchanged.
	// Elements not part of the LNS will be put in the "dropped" vector.

	let mut dropped = Vec::new();
	let mut num_dropped_in_row = 0;
	let mut write = 0; // Index of where to write the next element to keep.
	let mut read  = 0; // Index of the input stream.
	// Invariant: write <= read

	/// This speeds up well-ordered input by quite a lot.
	const DOUBLE_COMPARISONS : bool = true;

	/// Low RECENCY = faster when there is low randomness (a lot of order).
	/// High RECENCY = more resilient against long stretches of noise.
	/// If RECENCY is too small we are more dependent on nice data/luck.
	const RECENCY : usize = 8;

	while read < list.len() {
		if 1 <= write && compare(&list[read], &list[write - 1]) == Ordering::Less {
			// The next element is smaller than the last stored one.
			// The question is - should we drop the new element, or was accepting the previous element a mistake?

			/*
			   Check this situation:
			   0 1 2 3 9 5 6 7  (the 9 is a one-off)
			           | |
			           | read
			           write - 1
				Checking this improves performance because we catch common problems earlier (without back-tracking).
			*/
			if DOUBLE_COMPARISONS && num_dropped_in_row == 0 &&
			   2 <= write && compare(&list[read], &list[write - 2]) != Ordering::Less
			{
				// Quick undo: drop previously accepted element, and overwrite with new one:
				dropped.push(list[write - 1]);
				list[write - 1] = list[read];
				read += 1;
				continue;
			}

			if num_dropped_in_row < RECENCY {
				dropped.push(list[read]);
				read += 1;
				num_dropped_in_row += 1;
			} else {
				/*
				We accepted something num_dropped_in_row elements back that made us drop all RECENCY subsequent items.
				Accepting that element was obviously a mistake - so let's undo it!

				Example problem (RECENCY = 3):    0 1 12 3 4 5 6
					0 1 12 is accepted. 3, 4, 5 will be rejected because they are larger than the last kept item (12).
					When we get to 5 we reach num_dropped_in_row == RECENCY.
					This will trigger an undo where we drop the 12.
					When we again go to 3, we will keep it because it is larger than the last kept item (1).

				Example worst-case (RECENCY = 3):   ...100 101 102 103 104 1 2 3 4 5 ....
					100-104 is accepted. When we get to 3 we reach num_dropped_in_row == RECENCY.
					We drop 104 and reset the read by RECENCY. We restart, and then we drop again.
					This can lead us to backtracking RECENCY number of elements
					as many times as the leading non-decreasing subsequence is long.
				*/

				// Back up and recheck the elements we previously dropped:
				let trunc_to_length = dropped.len() - num_dropped_in_row;
				dropped.truncate(trunc_to_length);
				read -= num_dropped_in_row;

				// Drop the element we mistakingly accepted:
				dropped.push(list[write - 1]);
				write -= 1; // Over-write the dropped element.

				num_dropped_in_row = 0;
			}
		} else {
			// Keep:
			list[write] = list[read];
			read += 1;
			write += 1;
			num_dropped_in_row = 0;
		}
	}

	// ------------------------------------------------------------------------

	dropped.sort_by(|a, b| return compare(a, b));

	// ------------------------------------------------------------------------
	// list[..write] is now sorted, as is "dropped".
	// We now want to merge these into "list".
	// Let us do that from the back, putting the largest elements in place first:

	let mut back = list.len();

	while let Some(&last_dropped) = dropped.last() {
		while 0 < write && compare(&last_dropped, &list[write - 1]) == Ordering::Less {
			list[back - 1] = list[write - 1];
			back -= 1;
			write -= 1;
		}
		list[back - 1] = last_dropped;
		back -= 1;
		dropped.pop();
	}
}

fn drop_merge_sort<T>(list: &mut [T]) where T: Copy + Ord
{
	drop_merge_sort_by(list, |a, b| a.cmp(b))
}

// ----------------------------------------------------------------------------

type Element = i32;

/// Returns a mostly-sorted array with randomization_factor fraction of elements with random values.
fn generate_test_data(rng: &mut rand::StdRng, length: usize, randomization_factor: f32) -> Vec<Element> {
	let mut result = Vec::with_capacity(length);
	for i in 0..length {
		if rng.next_f32() < randomization_factor {
			result.push(rng.gen_range(0 as Element, length as Element));
		} else {
			result.push(i as Element);
		}
	}
	result
}

fn time_sort_ms<Sorter>(unsorted: &Vec<Element>, mut sorter: Sorter) -> (f32, Vec<Element>)
	where Sorter: FnMut(&mut Vec<Element>)
{
	let mut best_ns = None;
	let mut sorted = Vec::new();

	// Return fastest of five:
	for _ in 0..5 {
		let mut vec_clone = unsorted.clone();
		let start_time_ns = time::precise_time_ns();
		sorter(&mut vec_clone);
		let duration_ns = time::precise_time_ns() - start_time_ns;
		if best_ns == None || duration_ns < best_ns.unwrap() {
			best_ns = Some(duration_ns);
		}
		sorted = vec_clone;
	}

	(best_ns.unwrap() as f32 / 1000000.0, sorted)
}

fn generate_comparison_data(rng: &mut rand::StdRng, length: usize) {
	let num_samples : u64 = 1000;

	let mut pb = ProgressBar::new(num_samples);
	pb.message("Benchmarking: ");
	let mut std_file         = File::create(&Path::new("data/std_sort.data")).unwrap();
	let mut quicker_file     = File::create(&Path::new("data/quicker_sort.data")).unwrap();
	let mut drop_file        = File::create(&Path::new("data/drop_merge_sort.data")).unwrap();
	let mut speedup_file     = File::create(&Path::new("data/speedup_over_quickersort.data")).unwrap();

	for i in 0i32..(num_samples as i32 + 1) {
		let randomization_factor = (i as f32) / (num_samples as f32);
		let vec = generate_test_data(rng, length, randomization_factor);
		let (std_duration_ms,     std_sorted)     = time_sort_ms(&vec, |x| x.sort());
		let (quicker_duration_ms, quicker_sorted) = time_sort_ms(&vec, |x| quickersort::sort(x));
		let (drop_duration_ms,    drop_sorted)    = time_sort_ms(&vec, |x| drop_merge_sort(x));

		assert_eq!(std_sorted, drop_sorted);
		assert_eq!(quicker_sorted, drop_sorted);

		write!(std_file,     "{} {}\n", randomization_factor * 100.0, std_duration_ms).unwrap();
		write!(quicker_file, "{} {}\n", randomization_factor * 100.0, quicker_duration_ms).unwrap();
		write!(drop_file,    "{} {}\n", randomization_factor * 100.0, drop_duration_ms).unwrap();
		write!(speedup_file, "{} {}\n", randomization_factor * 100.0, quicker_duration_ms / drop_duration_ms).unwrap();

		pb.inc();
	}
}

#[test]
fn run_tests() {
	fn test(mut list: Vec<Element>) {
		let mut std_sorted = list.clone();
		std_sorted.sort();

		println!();
		println!("IN:  {:?}", list);
		drop_merge_sort(&mut list);
		println!("OUT: {:?}", list);

		if list != std_sorted {
			panic!("FAIL with input {:?}", list);
		}
	}

	test(vec!());
	test(vec!(0));
	test(vec!(0, 1));
	test(vec!(1, 0));
	test(vec!(0, 1, 2));
	test(vec!(0, 2, 1));
	test(vec!(1, 0, 2));
	test(vec!(1, 2, 0));
	test(vec!(2, 0, 1));
	test(vec!(2, 1, 0));
	test(vec!(0, 1, 3, 2, 4, -5, 6, 7, 8, 9));
	test(vec!(0, 1, 10, 3, 4, 5, 6, 7, 8, 9));
	test(vec!(10, 1, 2, 3, 4, 5, 6, 7, 8, 9));
	test(vec!(0, 0, 2, 3, 4, 1, 6, 1, 8, 9));
	test(vec!(20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19));
	test(vec!(20, 21, 2, 23, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19));
}

/// Benchmark worst-case input for Drop-Merge sort
fn bench_evil() {
	let evil_input : Vec<_> = (100..1000000).chain(0..100).collect();
	let (std_duration_ms, std_sorted)         = time_sort_ms(&evil_input, |x| x.sort());
	let (quicker_duration_ms, quicker_sorted) = time_sort_ms(&evil_input, |x| quickersort::sort(x));
	let (drop_duration_ms,    drop_sorted)    = time_sort_ms(&evil_input, |x| drop_merge_sort(x));

	assert_eq!(std_sorted, drop_sorted);
	assert_eq!(quicker_sorted, drop_sorted);
	println!("Worst-case input:");
	println!("std::sort:       {} ms", std_duration_ms);
	println!("Quicksort:       {} ms", quicker_duration_ms);
	println!("Drop-Merge sort: {} ms", drop_duration_ms);
}

fn main() {
	bench_evil();
	let seed: &[_] = &[0];
	let mut rng: StdRng = SeedableRng::from_seed(seed);
	generate_comparison_data(&mut rng, 1000000); // ~15 min runtime
}
<|endoftext|>


<commit_before>extern crate hyper;
extern crate rustc_serialize;
extern crate env_logger;

use std::env;
use std::io::{self, Read};
use std::str;

use rustc_serialize::json;

fn main() {
    env_logger::init().unwrap();
    let port = get_port();
    println!("Binding to PORT {}", port);
    let listening = hyper::Server::new(handle)
        .listen(("127.0.0.1", port)).unwrap();
    println!("Listening on http://{}", listening.socket);
}

const DEFAULT_PORT: u16 = 8080;

fn get_port() -> u16 {
    match env::var("PORT") {
        Ok(val) => u16::from_str_radix(&val, 10).unwrap_or(DEFAULT_PORT),
        Err(_) => DEFAULT_PORT
    }
}
fn handle(req: hyper::server::Request, mut res: hyper::server::Response<hyper::net::Fresh>) {
    match (req.method, req.uri) {
        (hyper::Get, hyper::uri::RequestUri::AbsolutePath(path)) => {
            let version = match lookup(&path[1..]) {
                Ok(v) => v,
                Err(..) => return not_found(res)
            };
            let badge = format!("https://img.shields.io/badge/crates.io-{}-green.svg", version);
            *res.status_mut() = hyper::status::StatusCode::Found;
            res.headers_mut().set(hyper::header::Location(badge));
            let _ = res.start().and_then(|res| res.end());
        },
        _ => not_found(res)
    };
}

fn not_found(mut res: hyper::server::Response<hyper::net::Fresh>) {
    *res.status_mut() = hyper::NotFound;
    let _ = res.start().and_then(|res| res.end());
}

type LookupResult = Result<String, LookupError>;

enum LookupError {
    NotFound,
    Http(hyper::HttpError),
    Io(io::Error)
}

impl From<hyper::HttpError> for LookupError {
    fn from(e: hyper::HttpError) -> LookupError {
        LookupError::Http(e)
    }
}

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

#[derive(Debug)]
struct LookupCrate {
    version: String
}

impl rustc_serialize::Decodable for LookupCrate {
    fn decode<D: rustc_serialize::Decoder>(d: &mut D) -> Result<LookupCrate, D::Error> {
        Ok(LookupCrate {
            version: try!(d.read_struct("", 1, |d| {
                d.read_struct_field("crate", 1, |d| {
                    d.read_struct("", 1, |d| {
                        d.read_struct_field("max_version", 0, |d| d.read_str())
                    })
                })
            }))
        })
    }
}
fn lookup(krate: &str) -> LookupResult {
    let mut client = hyper::Client::new();
    let url = format!("https://crates.io/api/v1/crates/{}", krate);
    let mut res = try!(client.get(&*url).send());
    if res.status != hyper::Ok {
        return Err(LookupError::NotFound);
    }

    let mut buf = Vec::new();
    try!(res.read_to_end(&mut buf));

    let text = match str::from_utf8(&buf) {
        Ok(text) => text,
        _ => return Err(LookupError::NotFound)
    };

    let map: LookupCrate = match json::decode(text) {
        Ok(map) => map,
        _ => return Err(LookupError::NotFound)
    };

    Ok(map.version)
}
<commit_msg>use socket 0.0.0.0<commit_after>extern crate hyper;
extern crate rustc_serialize;
extern crate env_logger;

use std::env;
use std::io::{self, Read};
use std::str;

use rustc_serialize::json;

fn main() {
    env_logger::init().unwrap();
    let port = get_port();
    println!("Binding to PORT {}", port);
    let listening = hyper::Server::new(handle)
        .listen(("0.0.0.0", port)).unwrap();
    println!("Listening on http://{}", listening.socket);
}

const DEFAULT_PORT: u16 = 8080;

fn get_port() -> u16 {
    match env::var("PORT") {
        Ok(val) => u16::from_str_radix(&val, 10).unwrap_or(DEFAULT_PORT),
        Err(_) => DEFAULT_PORT
    }
}
fn handle(req: hyper::server::Request, mut res: hyper::server::Response<hyper::net::Fresh>) {
    match (req.method, req.uri) {
        (hyper::Get, hyper::uri::RequestUri::AbsolutePath(path)) => {
            let version = match lookup(&path[1..]) {
                Ok(v) => v,
                Err(..) => return not_found(res)
            };
            let badge = format!("https://img.shields.io/badge/crates.io-{}-green.svg", version);
            *res.status_mut() = hyper::status::StatusCode::Found;
            res.headers_mut().set(hyper::header::Location(badge));
            let _ = res.start().and_then(|res| res.end());
        },
        _ => not_found(res)
    };
}

fn not_found(mut res: hyper::server::Response<hyper::net::Fresh>) {
    *res.status_mut() = hyper::NotFound;
    let _ = res.start().and_then(|res| res.end());
}

type LookupResult = Result<String, LookupError>;

enum LookupError {
    NotFound,
    Http(hyper::HttpError),
    Io(io::Error)
}

impl From<hyper::HttpError> for LookupError {
    fn from(e: hyper::HttpError) -> LookupError {
        LookupError::Http(e)
    }
}

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

#[derive(Debug)]
struct LookupCrate {
    version: String
}

impl rustc_serialize::Decodable for LookupCrate {
    fn decode<D: rustc_serialize::Decoder>(d: &mut D) -> Result<LookupCrate, D::Error> {
        Ok(LookupCrate {
            version: try!(d.read_struct("", 1, |d| {
                d.read_struct_field("crate", 1, |d| {
                    d.read_struct("", 1, |d| {
                        d.read_struct_field("max_version", 0, |d| d.read_str())
                    })
                })
            }))
        })
    }
}
fn lookup(krate: &str) -> LookupResult {
    let mut client = hyper::Client::new();
    let url = format!("https://crates.io/api/v1/crates/{}", krate);
    let mut res = try!(client.get(&*url).send());
    if res.status != hyper::Ok {
        return Err(LookupError::NotFound);
    }

    let mut buf = Vec::new();
    try!(res.read_to_end(&mut buf));

    let text = match str::from_utf8(&buf) {
        Ok(text) => text,
        _ => return Err(LookupError::NotFound)
    };

    let map: LookupCrate = match json::decode(text) {
        Ok(map) => map,
        _ => return Err(LookupError::NotFound)
    };

    Ok(map.version)
}
<|endoftext|>


<commit_before>use rand::{Rng, XorShiftRng};
use std::ops::{Add,Sub,Mul,Div};

#[derive(Debug,Clone,Copy)]
pub struct Vec3d {
    pub x: f64,
    pub y: f64,
    pub z: f64
}

pub trait Clamp {
    fn clamp(self) -> Self;
}

impl Clamp for f64 {
    #[inline]
    fn clamp(self) -> f64 {
        if self < 0.0 { 0.0 } else if self > 1.0 { 1.0 } else { self }
    }
}

impl Vec3d {
    #[inline]
    pub fn normalized(self) -> Vec3d {
        self / self.dot(self).sqrt()
    }
    #[inline]
    pub fn dot(self, other: Vec3d) -> f64 {
        self.x * other.x + self.y * other.y + self.z * other.z
    }
    #[inline]
    pub fn length_squared(self) -> f64 {
        self.x * self.x + self.y * self.y + self.z * self.z
    }
    #[inline]
    pub fn new(x: f64, y: f64, z: f64) -> Vec3d {
        Vec3d { x: x, y: y, z: z }
    }
    #[inline]
    pub fn zero() -> Vec3d {
        Vec3d { x: 0.0, y: 0.0, z: 0.0 }
    }
    #[inline]
    pub fn cross(self, other: Vec3d) -> Vec3d {
        Vec3d {
            x: self.y * other.z - self.z * other.y,
            y: self.z * other.x - self.x * other.z,
            z: self.x * other.y - self.y * other.x
        }
    }
    #[inline]
    pub fn max_component(self) -> f64 {
        if self.x > self.y && self.x > self.z { self.x }
        else if self.y > self.x && self.y > self.z { self.y }
        else { self.z }
    }
    #[inline]
    pub fn neg(self) -> Vec3d {
        Vec3d { x: -self.x, y: -self.y, z: -self.z }
    }
    #[inline]
    pub fn clamp(self) -> Vec3d {
        Vec3d { x: self.x.clamp(), y: self.y.clamp(), z: self.z.clamp() }
    }
}

impl Add for Vec3d {
    type Output = Vec3d;

    #[inline]
    fn add(self, other: Vec3d) -> Vec3d {
        Vec3d { x: self.x + other.x, y: self.y + other.y, z: self.z + other.z }
    }
}

impl Sub for Vec3d {
    type Output = Vec3d;

    #[inline]
    fn sub(self, other: Vec3d) -> Vec3d {
        Vec3d { x: self.x - other.x, y: self.y - other.y, z: self.z - other.z }
    }
}

impl Mul for Vec3d {
    type Output = Vec3d;

    #[inline]
    fn mul(self, other: Vec3d) -> Vec3d {
        Vec3d { x: self.x * other.x, y: self.y * other.y, z: self.z * other.z }
    }
}

impl Mul<f64> for Vec3d {
    type Output = Vec3d;

    #[inline]
    fn mul(self, other: f64) -> Vec3d {
        Vec3d { x: self.x * other, y: self.y * other, z: self.z * other }
    }
}

impl Div<f64> for Vec3d {
    type Output = Vec3d;

    #[inline]
    fn div(self, other: f64) -> Vec3d {
        let recip = 1.0 / other;
        Vec3d { x: self.x * recip, y: self.y * recip, z: self.z * recip }
    }
}

pub trait F64Rng {
    fn next(&mut self) -> f64;
}

impl F64Rng for XorShiftRng {
    fn next(&mut self) -> f64 {
        return self.gen::<f64>();
    }
}

<commit_msg>Divs, some inlining<commit_after>use rand::{Rng, XorShiftRng};
use std::ops::{Add,Sub,Mul,Div};

#[derive(Debug,Clone,Copy)]
pub struct Vec3d {
    pub x: f64,
    pub y: f64,
    pub z: f64
}

pub trait Clamp {
    fn clamp(self) -> Self;
}

impl Clamp for f64 {
    #[inline]
    fn clamp(self) -> f64 {
        if self < 0.0 { 0.0 } else if self > 1.0 { 1.0 } else { self }
    }
}

impl Vec3d {
    #[inline]
    pub fn normalized(self) -> Vec3d {
        self / self.dot(self).sqrt()
    }
    #[inline]
    pub fn dot(self, other: Vec3d) -> f64 {
        self.x * other.x + self.y * other.y + self.z * other.z
    }
    #[inline]
    pub fn length_squared(self) -> f64 {
        self.x * self.x + self.y * self.y + self.z * self.z
    }
    #[inline]
    pub fn length(self) -> f64 {
        (self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
    }
    #[inline]
    pub fn new(x: f64, y: f64, z: f64) -> Vec3d {
        Vec3d { x: x, y: y, z: z }
    }
    #[inline]
    pub fn zero() -> Vec3d {
        Vec3d { x: 0.0, y: 0.0, z: 0.0 }
    }
    #[inline]
    pub fn one() -> Vec3d {
        Vec3d { x: 1.0, y: 1.0, z: 1.0 }
    }
    #[inline]
    pub fn cross(self, other: Vec3d) -> Vec3d {
        Vec3d {
            x: self.y * other.z - self.z * other.y,
            y: self.z * other.x - self.x * other.z,
            z: self.x * other.y - self.y * other.x
        }
    }
    #[inline]
    pub fn max_component(self) -> f64 {
        if self.x > self.y && self.x > self.z { self.x }
        else if self.y > self.x && self.y > self.z { self.y }
        else { self.z }
    }
    #[inline]
    pub fn max_ordinal(self) -> u8 {
        if self.x > self.y && self.x > self.z { 0 }
        else if self.y > self.x && self.y > self.z { 1 }
        else { 2 }
    }
    #[inline]
    pub fn min_component(self) -> f64 {
        if self.x < self.y && self.x < self.z { self.x }
        else if self.y < self.x && self.y < self.z { self.y }
        else { self.z }
    }
    #[inline]
    pub fn min_ordinal(self) -> u8 {
        if self.x < self.y && self.x < self.z { 0 }
        else if self.y < self.x && self.y < self.z { 1 }
        else { 2 }
    }
    #[inline]
    pub fn abs(self) -> Vec3d {
        Vec3d { x: self.x.abs(), y: self.y.abs(), z: self.z.abs() }
    }
    #[inline]
    pub fn neg(self) -> Vec3d {
        Vec3d { x: -self.x, y: -self.y, z: -self.z }
    }
    #[inline]
    pub fn clamp(self) -> Vec3d {
        Vec3d { x: self.x.clamp(), y: self.y.clamp(), z: self.z.clamp() }
    }
    #[inline]
    pub fn min(self, other: Vec3d) -> Vec3d {
        Vec3d { x: self.x.min(other.x), y: self.y.min(other.y), z: self.z.min(other.z) }
    }
    #[inline]
    pub fn max(self, other: Vec3d) -> Vec3d {
        Vec3d { x: self.x.max(other.x), y: self.y.max(other.y), z: self.z.max(other.z) }
    }
}

impl Add for Vec3d {
    type Output = Vec3d;

    #[inline]
    fn add(self, other: Vec3d) -> Vec3d {
        Vec3d { x: self.x + other.x, y: self.y + other.y, z: self.z + other.z }
    }
}

impl Sub for Vec3d {
    type Output = Vec3d;

    #[inline]
    fn sub(self, other: Vec3d) -> Vec3d {
        Vec3d { x: self.x - other.x, y: self.y - other.y, z: self.z - other.z }
    }
}

impl Mul for Vec3d {
    type Output = Vec3d;

    #[inline]
    fn mul(self, other: Vec3d) -> Vec3d {
        Vec3d { x: self.x * other.x, y: self.y * other.y, z: self.z * other.z }
    }
}

impl Mul<f64> for Vec3d {
    type Output = Vec3d;

    #[inline]
    fn mul(self, other: f64) -> Vec3d {
        Vec3d { x: self.x * other, y: self.y * other, z: self.z * other }
    }
}

impl Div for Vec3d {
    type Output = Vec3d;

    #[inline]
    fn div(self, other: Vec3d) -> Vec3d {
        Vec3d { x: self.x / other.x, y: self.y / other.y, z: self.z * other.z }
    }
}

impl Div<f64> for Vec3d {
    type Output = Vec3d;

    #[inline]
    fn div(self, other: f64) -> Vec3d {
        let recip = 1.0 / other;
        Vec3d { x: self.x * recip, y: self.y * recip, z: self.z * recip }
    }
}

pub trait F64Rng {
    fn next(&mut self) -> f64;
}

impl F64Rng for XorShiftRng {
    fn next(&mut self) -> f64 {
        return self.gen::<f64>();
    }
}

<|endoftext|>


<commit_before>// spell-checker:ignore (words) araba newroot userspec

use crate::common::util::*;

#[test]
fn test_missing_operand() {
    let result = new_ucmd!().run();

    assert!(result
        .stderr_str()
        .starts_with("error: The following required arguments were not provided"));

    assert!(result.stderr_str().contains("<newroot>"));
}

#[test]
fn test_enter_chroot_fails() {
    let (at, mut ucmd) = at_and_ucmd!();

    at.mkdir("jail");

    let result = ucmd.arg("jail").fails();

    assert!(result
        .stderr_str()
        .starts_with("chroot: cannot chroot to 'jail': Operation not permitted (os error 1)"));
}

#[test]
fn test_no_such_directory() {
    let (at, mut ucmd) = at_and_ucmd!();

    at.touch(&at.plus_as_string("a"));

    ucmd.arg("a")
        .fails()
        .stderr_is("chroot: cannot change root directory to 'a': no such directory");
}

#[test]
fn test_invalid_user_spec() {
    let (at, mut ucmd) = at_and_ucmd!();

    at.mkdir("a");

    let result = ucmd.arg("a").arg("--userspec=ARABA:").fails();

    assert!(result.stderr_str().starts_with("chroot: invalid userspec"));
}

#[test]
fn test_preference_of_userspec() {
    let scene = TestScenario::new(util_name!());
    let result = scene.cmd("whoami").run();
    if is_ci() && result.stderr_str().contains("No such user/group") {
        // In the CI, some server are failing to return whoami.
        // As seems to be a configuration issue, ignoring it
        return;
    }
    println!("result.stdout = {}", result.stdout_str());
    println!("result.stderr = {}", result.stderr_str());
    let username = result.stdout_str().trim_end();

    let ts = TestScenario::new("id");
    let result = ts.cmd("id").arg("-g").arg("-n").run();
    println!("result.stdout = {}", result.stdout_str());
    println!("result.stderr = {}", result.stderr_str());

    if is_ci() && result.stderr_str().contains("cannot find name for user ID") {
        // In the CI, some server are failing to return id.
        // As seems to be a configuration issue, ignoring it
        return;
    }

    let group_name = result.stdout_str().trim_end();
    let (at, mut ucmd) = at_and_ucmd!();

    at.mkdir("a");

    let result = ucmd
        .arg("a")
        .arg("--user")
        .arg("fake")
        .arg("-G")
        .arg("ABC,DEF")
        .arg(format!("--userspec={}:{}", username, group_name))
        .run();

    println!("result.stdout = {}", result.stdout_str());
    println!("result.stderr = {}", result.stderr_str());
}

#[test]
fn test_default_shell() {
    let ts = TestScenario::new(util_name!());
    let at = &ts.fixtures;

    let dir = "CHROOT_DIR";
    at.mkdir(dir);

    let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
    let expected = format!(
        "chroot: failed to run command '{}': No such file or directory",
        shell
    );

    if let Ok(result) = run_ucmd_as_root(&ts, &[dir]) {
        result.stderr_contains(expected);
    } else {
        print!("TEST SKIPPED");
    }
}
<commit_msg>test_chroot: add comments<commit_after>// spell-checker:ignore (words) araba newroot userspec

use crate::common::util::*;

#[test]
fn test_missing_operand() {
    let result = new_ucmd!().run();

    assert!(result
        .stderr_str()
        .starts_with("error: The following required arguments were not provided"));

    assert!(result.stderr_str().contains("<newroot>"));
}

#[test]
fn test_enter_chroot_fails() {
    // NOTE: since #2689 this test also ensures that we don't regress #2687
    let (at, mut ucmd) = at_and_ucmd!();

    at.mkdir("jail");

    let result = ucmd.arg("jail").fails();

    assert!(result
        .stderr_str()
        .starts_with("chroot: cannot chroot to 'jail': Operation not permitted (os error 1)"));
}

#[test]
fn test_no_such_directory() {
    let (at, mut ucmd) = at_and_ucmd!();

    at.touch(&at.plus_as_string("a"));

    ucmd.arg("a")
        .fails()
        .stderr_is("chroot: cannot change root directory to 'a': no such directory");
}

#[test]
fn test_invalid_user_spec() {
    let (at, mut ucmd) = at_and_ucmd!();

    at.mkdir("a");

    let result = ucmd.arg("a").arg("--userspec=ARABA:").fails();

    assert!(result.stderr_str().starts_with("chroot: invalid userspec"));
}

#[test]
fn test_preference_of_userspec() {
    let scene = TestScenario::new(util_name!());
    let result = scene.cmd("whoami").run();
    if is_ci() && result.stderr_str().contains("No such user/group") {
        // In the CI, some server are failing to return whoami.
        // As seems to be a configuration issue, ignoring it
        return;
    }
    println!("result.stdout = {}", result.stdout_str());
    println!("result.stderr = {}", result.stderr_str());
    let username = result.stdout_str().trim_end();

    let ts = TestScenario::new("id");
    let result = ts.cmd("id").arg("-g").arg("-n").run();
    println!("result.stdout = {}", result.stdout_str());
    println!("result.stderr = {}", result.stderr_str());

    if is_ci() && result.stderr_str().contains("cannot find name for user ID") {
        // In the CI, some server are failing to return id.
        // As seems to be a configuration issue, ignoring it
        return;
    }

    let group_name = result.stdout_str().trim_end();
    let (at, mut ucmd) = at_and_ucmd!();

    at.mkdir("a");

    let result = ucmd
        .arg("a")
        .arg("--user")
        .arg("fake")
        .arg("-G")
        .arg("ABC,DEF")
        .arg(format!("--userspec={}:{}", username, group_name))
        .run();

    println!("result.stdout = {}", result.stdout_str());
    println!("result.stderr = {}", result.stderr_str());
}

#[test]
fn test_default_shell() {
    // NOTE: This test intends to trigger code which can only be reached with root permissions.
    let ts = TestScenario::new(util_name!());
    let at = &ts.fixtures;

    let dir = "CHROOT_DIR";
    at.mkdir(dir);

    let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
    let _expected = format!(
        "chroot: failed to run command '{}': No such file or directory",
        shell
    );

    // TODO: [2021-09; jhscheer] uncomment if/when #2692 gets merged
    // if let Ok(result) = run_ucmd_as_root(&ts, &[dir]) {
    //     result.stderr_contains(expected);
    // } else {
    //     print!("TEST SKIPPED");
    // }
}
<|endoftext|>


<commit_before>
extern crate cdrs;
#[macro_use]
extern crate log;
extern crate env_logger;

use cdrs::client::CDRS;
use cdrs::query::QueryBuilder;
use cdrs::authenticators::NoneAuthenticator;
use cdrs::compression::Compression;
use cdrs::consistency::Consistency;
use cdrs::transport::TransportPlain;
use cdrs::query::QueryParamsBuilder;
use cdrs::types::value::Value;

// default credentials
const _ADDR: &'static str = "127.0.0.1:9042";

pub struct TestContext {
    pub client: cdrs::client::CDRS<NoneAuthenticator, TransportPlain>,
}

impl TestContext {
    fn new() -> TestContext {
        const _ADDR: &'static str = "127.0.0.1:9042";
        let authenticator = NoneAuthenticator;
        let tcp_transport = TransportPlain::new(_ADDR);
        let client = CDRS::new(tcp_transport.unwrap(), authenticator);
        TestContext { client: client }
    }
}

#[test]
fn create_keyspace() {
    let ctx = TestContext::new();
    let mut session = ctx.client.start(Compression::None).unwrap();
    let create_ks_cql = "CREATE KEYSPACE user_keyspace WITH REPLICATION = { 'class' : \
                         'SimpleStrategy', 'replication_factor' : 1 } ;";

    let create_ks_query = QueryBuilder::new(create_ks_cql)
        .consistency(Consistency::One)
        .finalize();

    let create_ks_query_result = session.query(create_ks_query, false, false);

    assert_eq!(create_ks_query_result.is_ok(), true);

}

#[test]
fn create_table() {
    let ctx = TestContext::new();
    let mut session = ctx.client.start(Compression::None).unwrap();
    let create_table_cql = "CREATE TABLE user_keyspace.users (
        user_name varchar PRIMARY KEY,
        password varchar,
        gender varchar,
        session_token varchar,
        state varchar,
        birth_year bigint
    );";

    let create_table_query = QueryBuilder::new(create_table_cql)
        .consistency(Consistency::One)
        .finalize();

    let create_table_query_result = session.query(create_table_query, false, false);

    assert_eq!(create_table_query_result.is_ok(), true);

    match create_table_query_result {
        Ok(ref res) => println!("table created: {:?}", res.get_body()),
        Err(ref err) => println!("Error occured: {:?}", err),
    }

}

#[test]
fn insert_data_users() {

    let ctx = TestContext::new();
    let mut session = ctx.client.start(Compression::None).unwrap();
    let insert_table_cql = " insert into user_keyspace.users
            (user_name ,password,gender,session_token, state)
    values  (?         ,  ?     ,     ?,            ?,     ?)";

    let prepared = session.prepare(insert_table_cql.to_string(), true, true)
        .unwrap()
        .get_body()
        .into_prepared()
        .unwrap();

    println!("prepared:\n{:?}", prepared);

    let v: Vec<Value> = vec![Value::new_normal(String::from("harry").into_bytes()),
                             Value::new_normal(String::from("pwd").into_bytes()),
                             Value::new_normal(String::from("male").into_bytes()),
                             Value::new_normal(String::from("09000").into_bytes()),
                             Value::new_normal(String::from("FL").into_bytes())];
    let execution_params = QueryParamsBuilder::new(Consistency::One).values(v).finalize();

    let query_id = prepared.id;
    //    let executed = session.execute(query_id, execution_params, true, true)
    //        .unwrap()
    //        .get_body()
    //        .into_set_keyspace()
    //        .unwrap();

    let executed = session.execute(query_id, execution_params, true, true);

    info!("executed:\n{:?}", executed);

}

//#[test]
//fn drop_keyspace() {
//    let ctx = TestContext::new();
//    let mut session = ctx.client.start(Compression::None).unwrap();
//    let drop_ks = "DROP KEYSPACE user_keyspace;";
//    let with_tracing = false;
//    let with_warnings = false;
//    let drop_ks_query = QueryBuilder::new(drop_ks).consistency(Consistency::One).finalize();
//    let drop_ks_query_result = session.query(drop_ks_query, with_tracing, with_warnings);
//
//    assert_eq!(drop_ks_query_result.is_ok(), true);
//
//    match drop_ks_query_result {
//        Ok(ref res) => println!("keyspace dropped: {:?}", res.get_body()),
//        Err(ref err) => println!("Error occured: {:?}", err),
//    }
//}
<commit_msg>updated test case<commit_after>
extern crate cdrs;
#[macro_use]
extern crate log;
extern crate env_logger;

use cdrs::client::CDRS;
use cdrs::query::QueryBuilder;
use cdrs::authenticators::NoneAuthenticator;
use cdrs::compression::Compression;
use cdrs::consistency::Consistency;
use cdrs::transport::TransportPlain;
use cdrs::query::QueryParamsBuilder;
use cdrs::types::value::Value;

// default credentials
const _ADDR: &'static str = "127.0.0.1:9042";

pub struct TestContext {
    pub client: cdrs::client::CDRS<NoneAuthenticator, TransportPlain>,
}

impl TestContext {
    fn new() -> TestContext {
        const _ADDR: &'static str = "127.0.0.1:9042";
        let authenticator = NoneAuthenticator;
        let tcp_transport = TransportPlain::new(_ADDR);
        let client = CDRS::new(tcp_transport.unwrap(), authenticator);
        TestContext { client: client }
    }
}

#[test]
fn create_keyspace() {
    let ctx = TestContext::new();
    let mut session = ctx.client.start(Compression::None).unwrap();
    let create_ks_cql = "CREATE KEYSPACE user_keyspace WITH REPLICATION = { 'class' : \
                         'SimpleStrategy', 'replication_factor' : 1 } ;";

    let create_ks_query = QueryBuilder::new(create_ks_cql)
        .consistency(Consistency::One)
        .finalize();

    let create_ks_query_result = session.query(create_ks_query, false, false);

    assert_eq!(create_ks_query_result.is_ok(), true);

}

#[test]
fn create_table() {
    let ctx = TestContext::new();
    let mut session = ctx.client.start(Compression::None).unwrap();
    let create_table_cql = "CREATE TABLE user_keyspace.users (
        user_name varchar PRIMARY KEY,
        password varchar,
        gender varchar,
        session_token varchar,
        state varchar,
        birth_year bigint
    );";

    let create_table_query = QueryBuilder::new(create_table_cql)
        .consistency(Consistency::One)
        .finalize();

    let create_table_query_result = session.query(create_table_query, false, false);

    assert_eq!(create_table_query_result.is_ok(), true);

    match create_table_query_result {
        Ok(ref res) => println!("table created: {:?}", res.get_body()),
        Err(ref err) => println!("Error occured: {:?}", err),
    }

}

#[test]
fn insert_data_users() {

    let ctx = TestContext::new();
    let mut session = ctx.client.start(Compression::None).unwrap();
    let insert_table_cql = " insert into user_keyspace.users
            (user_name ,password,gender,session_token, state)
    values  (?         ,  ?     ,     ?,            ?,     ?)";

    let prepared = session.prepare(insert_table_cql.to_string(), true, true)
        .unwrap()
        .get_body()
        .into_prepared()
        .unwrap();

    println!("prepared:\n{:?}", prepared);

    let v: Vec<Value> = vec![Value::new_normal(String::from("harry").into_bytes()),
                             Value::new_normal(String::from("pwd").into_bytes()),
                             Value::new_normal(String::from("male").into_bytes()),
                             Value::new_normal(String::from("09000").into_bytes()),
                             Value::new_normal(String::from("FL").into_bytes())];
    let execution_params = QueryParamsBuilder::new(Consistency::One).values(v).finalize();

    let query_id = prepared.id;
    //    let executed = session.execute(query_id, execution_params, true, true)
    //        .unwrap()
    //        .get_body()
    //        .into_set_keyspace()
    //        .unwrap();

    let executed = session.execute(query_id, execution_params, true, true);

    info!("executed:\n{:?}", executed);

}

#[test]
fn drop_keyspace() {
    let ctx = TestContext::new();
    let mut session = ctx.client.start(Compression::None).unwrap();
    let drop_ks = "DROP KEYSPACE user_keyspace;";
    let with_tracing = false;
    let with_warnings = false;
    let drop_ks_query = QueryBuilder::new(drop_ks).consistency(Consistency::One).finalize();
    let drop_ks_query_result = session.query(drop_ks_query, with_tracing, with_warnings);

    assert_eq!(drop_ks_query_result.is_ok(), true);

    match drop_ks_query_result {
        Ok(ref res) => println!("keyspace dropped: {:?}", res.get_body()),
        Err(ref err) => println!("Error occured: {:?}", err),
    }
}
<|endoftext|>


<commit_before>use std::fmt::{ Display, Formatter, Error };
use std::iter::Iterator;
use std::collections::HashMap;
use std::vec::Vec;
use std::io::prelude::*;
use std::io::BufReader;
use std::fs::File;

use std::clone::Clone;

/**
 * test can be deleted.
 * clauses will house all clauses, key is int so that we can use occurrences
 * ID for simplifications later
 * occurrences tracks which clauses literals are used in
 */
#[derive(Debug)]
pub struct CNF {
    nvar        : i32,
    nclause     : i32,
    clauses     : HashMap<i32, Vec<i32>>,
    occurrences : HashMap<i32, Vec<i32>>
}

impl CNF {
    pub fn new(nvar: i32, nclause: i32) -> CNF {
        CNF {
            nvar        : nvar,
            nclause     : nclause,
            clauses     : HashMap::new(),
            occurrences : HashMap::new()
        }
    }

    // Add a clause, return the ID of the inserted clause
    fn add_clause(&mut self, clause : Vec<i32>) -> i32 {
        let id : i32 = self.clauses.len() as i32;
        for var in &clause {
            let occ = self.occurrences.entry(*var).or_insert(Vec::new());
            occ.push(id);
        }
        self.clauses.insert(id, clause);
        id
    }

    pub fn to_string(self) -> String {
        format!("Nvar: {:?} Nclause: {:?}\nClauses: {:?}\nOccurrrences: {:?}",
                self.nvar, self.nclause,
                self.clauses, self.occurrences)
    }
}

impl Clone for CNF {
	fn clone(&self) -> CNF {
		CNF {
			nvar : self.nvar,
			nclause : self.nclause,
			clauses : self.clauses.clone(),
			occurrences : self.occurrences.clone(),
		}
	}
}

impl Display for CNF {
    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
        let mut comma_separated = String::new();
        comma_separated.push_str("Need to implement formatter");
        write!(f, "{}", comma_separated)
    }
}

// End CNF

// Begin Parsing

#[derive(Debug)]
pub enum SatError {
    InvalidSyntax,
	InvalidProblem
}

fn parse_dimacs(reader: &mut BufReader<File>) -> Result<CNF, SatError> {
    let mut line_iterator = reader.lines();

    let mut nvar: i32 = -1;
    let mut nclause: i32 = -1;
    // DIMACS file must have a problem statement before other lines.
    // This first loop searches for the problem statement.
    for line in &mut line_iterator {
        let line = line.expect("Could not read file");  // Unwrap result
        let words: Vec<&str> = line.split_whitespace().collect();
        match words[0] {
            "c" => { }
            "p" => { // Problem statement
                // Must have format "p cnf nvar nclause"
                if words.len() != 4 || words[1] != "cnf" {
                    return Err(SatError::InvalidSyntax);
                }
                nvar = words[2].parse()
                    .expect(&format!("Invalid number of variables {}", words[2]));
                nclause = words[3].parse()
                    .expect(&format!("Invalid number of clauses {}", words[3]));
                break;
            }
            _ => { return Err(SatError::InvalidSyntax); }
        }
    }
    // Then nvar, nclause were never initialized
    if nvar == -1 || nclause == -1 {
       return Err(SatError::InvalidProblem);
    }
    // TODO: Different errors for different descriptions (hence why I've split up this if
    // statement)
    if nvar == 0 || nclause == 0 {
        return Err(SatError::InvalidProblem)
    }

    // Initialize CNF and parse the rest of the file
    let mut cnf = CNF::new(nvar, nclause);
    for line in &mut line_iterator {
        let line = line.expect("Could not read filce");
        let words: Vec<&str> = line.split_whitespace().collect();
        match words[0] {
            "c" => { }
            _   => {
                // TODO: Assert valid set of tokens
                let tokens = words.iter()
                    .map(|s| s.parse().expect("Invalid DIMACS File"))
                    .collect();;
                println!("{:?}", tokens);
                cnf.add_clause(tokens);
            }
        }
    }
    Ok(cnf)
}

pub fn parse_dimacs_file(f: File) -> Result<CNF, SatError> {
    // Read the file
    let mut reader = BufReader::new(f);

    // TODO: This is definitely not the correct way to handle errors.
    // Should parse_dimacs have options for returning an error AND panicking?
    let line = parse_dimacs(&mut reader);
    line
}
<commit_msg>Parse according to 2n + 1 negation style<commit_after>use std::fmt::{ Display, Formatter, Error };
use std::iter::Iterator;
use std::collections::HashMap;
use std::vec::Vec;
use std::io::prelude::*;
use std::io::BufReader;
use std::fs::File;

use std::clone::Clone;

/**
 * test can be deleted.
 * clauses will house all clauses, key is int so that we can use occurrences
 * ID for simplifications later
 * occurrences tracks which clauses literals are used in
 */
#[derive(Debug)]
pub struct CNF {
    nvar        : i32,
    nclause     : i32,
    clauses     : HashMap<i32, Vec<i32>>,
    occurrences : HashMap<i32, Vec<i32>>
}

impl CNF {
    pub fn new(nvar: i32, nclause: i32) -> CNF {
        CNF {
            nvar        : nvar,
            nclause     : nclause,
            clauses     : HashMap::new(),
            occurrences : HashMap::new()
        }
    }

    // Add a clause, return the ID of the inserted clause
    fn add_clause(&mut self, clause : Vec<i32>) -> i32 {
        let id : i32 = self.clauses.len() as i32;
        for var in &clause {
            let occ = self.occurrences.entry(*var).or_insert(Vec::new());
            occ.push(id);
        }
        self.clauses.insert(id, clause);
        id
    }

    pub fn to_string(self) -> String {
        format!("Nvar: {:?} Nclause: {:?}\nClauses: {:?}\nOccurrrences: {:?}",
                self.nvar, self.nclause,
                self.clauses, self.occurrences)
    }
}

impl Clone for CNF {
    fn clone(&self) -> CNF {
        CNF {
            nvar : self.nvar,
            nclause : self.nclause,
            clauses : self.clauses.clone(),
            occurrences : self.occurrences.clone(),
        }
    }
}

impl Display for CNF {
    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
        let mut comma_separated = String::new();
        comma_separated.push_str("Need to implement formatter");
        write!(f, "{}", comma_separated)
    }
}

// End CNF

// Begin Parsing

#[derive(Debug)]
pub enum SatError {
    InvalidSyntax,
    InvalidProblem
}

fn parse_dimacs(reader: &mut BufReader<File>) -> Result<CNF, SatError> {
    let mut line_iterator = reader.lines();

    let mut nvar: i32 = -1;
    let mut nclause: i32 = -1;
    // DIMACS file must have a problem statement before other lines.
    // This first loop searches for the problem statement.
    for line in &mut line_iterator {
        let line = line.expect("Could not read file");  // Unwrap result
        let words: Vec<&str> = line.split_whitespace().collect();
        match words[0] {
            "c" => { }
            "p" => { // Problem statement
                // Must have format "p cnf nvar nclause"
                if words.len() != 4 || words[1] != "cnf" {
                    return Err(SatError::InvalidSyntax);
                }
                nvar = words[2].parse()
                    .expect(&format!("Invalid number of variables {}", words[2]));
                nclause = words[3].parse()
                    .expect(&format!("Invalid number of clauses {}", words[3]));
                break;
            }
            _ => { return Err(SatError::InvalidSyntax); }
        }
    }
    // Then nvar, nclause were never initialized
    if nvar == -1 || nclause == -1 {
       return Err(SatError::InvalidProblem);
    }
    // TODO: Different errors for different descriptions (hence why I've split up this if
    // statement)
    if nvar == 0 || nclause == 0 {
        return Err(SatError::InvalidProblem)
    }

    // Initialize CNF and parse the rest of the file
    let mut cnf = CNF::new(nvar, nclause);
    for line in &mut line_iterator {
        let line = line.expect("Could not read filce");
        let words: Vec<&str> = line.split_whitespace().collect();
        match words[0] {
            "c" => { }
            _   => {
                let tokens: Vec<i32> = words.iter()
                    .filter_map(|s| {
                        let n = s.parse::<i32>().expect("Invalid DIMACS File");
                        // FIXME: This ignores zeros not just as line enders but in the formulas
                        // themselves. TODO: Split on zeros at the end here.
                        if n == 0 { return None; }
                        if n > nvar { panic!(format!("Variable out of range: {}", n)); }
                        Some(if n < 0 { (-n) << 1 | 1 } else { n << 1 })
                    })
                    .collect();
                cnf.add_clause(tokens);
            }
        }
    }
    Ok(cnf)
}

pub fn parse_dimacs_file(f: File) -> Result<CNF, SatError> {
    // Read the file
    let mut reader = BufReader::new(f);

    // TODO: This is definitely not the correct way to handle errors.
    // Should parse_dimacs have options for returning an error AND panicking?
    let line = parse_dimacs(&mut reader);
    line
}
<|endoftext|>