<commit_before>extern crate iron;
extern crate router;
extern crate bodyparser;
extern crate backlogrs;
extern crate time;
extern crate "rustc-serialize" as rustc_serialize;

use backlogrs::*;
use backlogrs::models::*;
use iron::prelude::*;
use iron::{status};
use router::Router;
use rustc_serialize::json;
use std::str::FromStr;
use std::old_io::BufferedReader;
use std::old_io::ByRefReader;

struct DebugIronError;

impl iron::AfterMiddleware for DebugIronError {
    fn catch(&self, req: &mut Request, err: IronError) -> IronResult<Response> {
        // In case of error bubbling up
        Ok(Response::with(format!("{:?}", err)))
    }
}

fn main() {
    let mut router = Router::new();
    router.get("/user", get_users);
    // Users aren't allowed to update; as soon as a user
    // is created it is stuck that way. At least for now.
    router.post("/user", post_login);
    router.get("/user/:id", get_user_by_id);
    router.get("/user/:id/library", get_library);
    router.get("/user/:uid/library/:eid", get_entry);
    router.post("/user/:uid/library", post_entry);
    router.get("/game", get_games);
    router.get("/game/:id", get_game_by_id);
    router.get("/status", get_status);

    let mut chain = Chain::new(router);
    chain.link_before(Api);
    chain.around(DbConnection::new());
    // Prints the error in html body
    chain.link_after(DebugIronError);

    println!("Listening on port 3000...");
    Iron::new(chain).http("0.0.0.0:3000").unwrap();
}

fn post_entry(req: &mut Request) -> IronResult<Response> {
    let mut new_entry = try!(req.get::<bodyparser::Struct<Entry>>()
                         .on_err("bad request")).unwrap();
    let user_id: i32 = FromStr::from_str(req.extensions.find::<Router>().unwrap().find("uid").unwrap()).unwrap();
    let e = status::InternalServerError;

    let db = req.db();
    if let Some(entry_id) = new_entry.id {
        // The entry should be updated
        let stmt = try!(db.prepare(
                "SELECT e.* FROM Login lo JOIN Library li ON lo.id = li.login_id \
                JOIN Entry e ON e.id = li.entry_id WHERE e.id = $1 AND lo.id = $2")
            .on_err(e));
        let prev_entry = try!(stmt.query(&[&entry_id, &user_id]).on_err(e))
            .collect_sql::<Vec<Entry>>()[0].clone();

        if new_entry.status.is_none() {
            new_entry.status = prev_entry.status;
        }
        if new_entry.time_played.is_none() {
            new_entry.time_played = prev_entry.time_played;
        }

        try!(db.execute(
                "UPDATE Entry e SET status = $3, time_played = $4 WHERE e.id = $1 AND EXISTS \
                (SELECT * FROM Library li WHERE li.entry_id = $1 AND li.login_id = $2)",
                &[&entry_id, &user_id, &new_entry.status.unwrap(), &new_entry.time_played])
            .on_err(
                "failed, probably because name/email already exists"));
    } else {
        if new_entry.game_id.is_none() {
            return Err(IronError::new(LibError, "test!"));
        }
        if new_entry.status.is_none() {
            new_entry.status = Some(Status::PlanToPlay);
        }
        if new_entry.time_played.is_none() {
            new_entry.time_played = Some(0.0);
        }

        // Insert new entry
        // Create transaction and commit if everything went as expected
        let trans = try!(db.transaction().on_err("failed getting transaction"));
        // First create entry and then map that into a library
        let stmt = try!(trans.prepare(
                "INSERT INTO Entry (game_id, time_played, status) \
                VALUES ($1, $2, $3) RETURNING id")
            .on_err(e));
        let entry_id: i32 = try!(stmt.query(
                &[&new_entry.game_id, &new_entry.time_played, &new_entry.status.unwrap()])
            .on_err(e)).next().unwrap().get(0);

        // Create library entry
        try!(trans.execute(
                "INSERT INTO Library (entry_id, login_id) VALUES ($1, $2)",
                &[&entry_id, &user_id])
            .on_err(e));

        try!(trans.commit().on_err(e));
    }

    Ok(Response::with((status::Ok, Json(new_entry))))
}

fn post_login(req: &mut Request) -> IronResult<Response> {
    let login = try!(req.get::<bodyparser::Struct<Login>>()
                     .on_err("bad request")).unwrap();

    let db = req.db();

    try!(db.execute(
            "INSERT INTO Login (username, password, email) \
            VALUES ($1, $2, $3)",
            &[&login.username,
            &login.password,
            &login.email]).on_err(
                "failed, probably because name/email already exists"));

    Ok(Response::with((status::Ok, Json(login))))
}

fn get_status(req: &mut Request) -> IronResult<Response> {
    let db = req.db();
    let e = status::InternalServerError;

    let stmt = try!(db.prepare("SELECT unnest(enum_range(NULL::Status))").on_err(e));
    let res = try!(stmt.query(&[]).on_err(e)).map(|x| {
        x.get(0)
    }).collect::<Vec<Status>>();

    Ok(Response::with((status::Ok, Json(res))))
}

fn get_game_by_id(req: &mut Request) -> IronResult<Response> {
    let db = req.db();
    // TODO: Create an extension method to do this prettier
    let id: i32 = req.extensions.find::<Router>()
        .and_then(|x| x.find("id"))
        .and_then(|x| FromStr::from_str(x).ok()).unwrap();
    let e = status::InternalServerError;

    let stmt = try!(db.prepare("SELECT * FROM Game WHERE id = $1").on_err(e));
    let res = try!(stmt.query(&[&id]).on_err(("Oops", e))).collect_sql::<Vec<Game>>();

    Ok(Response::with((status::Ok, Json(res))))
}

fn get_games(req: &mut Request) -> IronResult<Response> {
    let db = req.db();
    let e = ("Database error!", status::InternalServerError);

    let stmt = try!(db.prepare("SELECT * FROM Game").on_err(e));
    let res = try!(stmt.query(&[]).on_err(e)).collect_sql::<Vec<Game>>();

    Ok(Response::with((status::Ok, Json(res))))
}

fn get_entry(req: &mut Request) -> IronResult<Response> {
    let db = req.db();
    let user_id: i32 = FromStr::from_str(req.extensions.find::<Router>().unwrap().find("uid").unwrap()).unwrap();
    let entry_id: i32 = FromStr::from_str(req.extensions.find::<Router>().unwrap().find("eid").unwrap()).unwrap();
    let e = status::InternalServerError;

    let stmt = try!(db.prepare(
            "SELECT e.* FROM Login lo JOIN Library li ON lo.id = li.login_id \
            JOIN Entry e ON e.id = li.entry_id WHERE e.id = $1 AND lo.id = $2").on_err(e));
    let res = try!(stmt.query(&[&entry_id, &user_id]).on_err(("Oops", e)))
        .collect_sql::<Vec<Entry>>();

    Ok(Response::with((status::Ok, Json(res))))
}

fn get_library(req: &mut Request) -> IronResult<Response> {
    let db = req.db();
    let query_str = req.extensions.find::<Router>().unwrap().find("id").unwrap_or("-1");
    let user_id: i32 = FromStr::from_str(query_str).unwrap();
    let e = status::InternalServerError;

    let stmt = try!(db.prepare(
            "SELECT e.* FROM Login lo JOIN Library li ON lo.id = li.login_id \
            JOIN Entry e ON e.id = li.entry_id WHERE lo.id = $1").on_err(e));
    let res = try!(stmt.query(&[&user_id]).on_err(("Oops", e)))
        .collect_sql::<Vec<Entry>>();

    Ok(Response::with((status::Ok, Json(res))))
}

fn get_user_by_id(req: &mut Request) -> IronResult<Response> {
    let db = req.db();
    let query_str = req.extensions.find::<Router>().and_then(|x| x.find("id")).unwrap_or("");
    println!("query_str: {:?}", query_str);

    let id: i32 = FromStr::from_str(query_str).unwrap();
    let e = status::InternalServerError;

    let stmt = try!(db.prepare("SELECT * FROM Login WHERE id = $1").on_err(e));
    let res = try!(stmt.query(&[&id]).on_err(("Oops", e))).collect_sql::<Vec<User>>();

    Ok(Response::with((status::Ok, Json(res))))
}

fn get_users(req: &mut Request) -> IronResult<Response> {
    let db = req.db();
    let e = ("Database error!", status::InternalServerError);

    let stmt = try!(db.prepare("SELECT * FROM Login").on_err(e));
    let res = try!(stmt.query(&[]).on_err(e)).collect_sql::<Vec<User>>();

    Ok(Response::with((status::Ok, Json(res))))
}
<commit_msg>(fix) crash on invalid input in URL. Closes #4.<commit_after>#![feature(core)]
extern crate iron;
extern crate router;
extern crate bodyparser;
extern crate backlogrs;
extern crate time;
extern crate "rustc-serialize" as rustc_serialize;

use backlogrs::*;
use backlogrs::models::*;
use iron::prelude::*;
use iron::{status};
use router::Router;
use std::str::FromStr;

struct DebugIronError;
impl iron::AfterMiddleware for DebugIronError {
    fn catch(&self, _: &mut Request, err: IronError) -> IronResult<Response> {
        // In case of error bubbling up
        Ok(Response::with(format!("{:?}", err)))
            // Ok(err.response)
    }
}

fn main() {
    let mut router = Router::new();
    router.get("/user", get_users);
    // Users aren't allowed to update; as soon as a user
    // is created it is stuck that way. At least for now.
    router.post("/user", post_login);
    router.get("/user/:id", get_user_by_id);
    router.get("/user/:id/library", get_library);
    router.get("/user/:uid/library/:eid", get_entry);
    router.post("/user/:id/library", post_entry);
    router.get("/game", get_games);
    router.get("/game/:id", get_game_by_id);
    router.get("/status", get_status);

    let mut chain = Chain::new(router);
    chain.link_before(Api);
    chain.around(DbConnection::new());
    // Prints the error in html body
    chain.link_after(DebugIronError);

    println!("Listening on port 3000...");
    Iron::new(chain).http("0.0.0.0:3000").unwrap();
}

fn post_entry(req: &mut Request) -> IronResult<Response> {
    let mut new_entry = try!(req.get::<bodyparser::Struct<Entry>>()
                             .on_err("bad request")).unwrap();
    let e = status::InternalServerError;
    let user_id: i32 = try!(req.extensions.find::<Router>()
                            .and_then(|x| x.find("id"))
                            .ok_or(LibError)
                            .and_then(|x| FromStr::from_str(x).map_err(|_| LibError))
                            .on_err(e));

    let db = req.db();
    if let Some(entry_id) = new_entry.id {
        // The entry should be updated
        let stmt = try!(db.prepare(
                "SELECT e.* FROM Login lo JOIN Library li ON lo.id = li.login_id \
                JOIN Entry e ON e.id = li.entry_id WHERE e.id = $1 AND lo.id = $2")
            .on_err(e));
        let prev_entry = try!(stmt.query(&[&entry_id, &user_id]).on_err(e))
            .collect_sql::<Vec<Entry>>()[0].clone();

        if new_entry.status.is_none() {
            new_entry.status = prev_entry.status;
        }
        if new_entry.time_played.is_none() {
            new_entry.time_played = prev_entry.time_played;
        }

        try!(db.execute(
                "UPDATE Entry e SET status = $3, time_played = $4 WHERE e.id = $1 AND EXISTS \
                (SELECT * FROM Library li WHERE li.entry_id = $1 AND li.login_id = $2)",
                &[&entry_id, &user_id, &new_entry.status.unwrap(), &new_entry.time_played])
            .on_err(
                "failed, probably because name/email already exists"));
    } else {
        if new_entry.game_id.is_none() {
            return Err(IronError::new(LibError, "test!"));
        }
        if new_entry.status.is_none() {
            new_entry.status = Some(Status::PlanToPlay);
        }
        if new_entry.time_played.is_none() {
            new_entry.time_played = Some(0.0);
        }

        // Insert new entry
        // Create transaction and commit if everything went as expected
        let trans = try!(db.transaction().on_err("failed getting transaction"));
        // First create entry and then map that into a library
        let stmt = try!(trans.prepare(
                "INSERT INTO Entry (game_id, time_played, status) \
                VALUES ($1, $2, $3) RETURNING id")
            .on_err(e));
        let entry_id: i32 = try!(stmt.query(
                &[&new_entry.game_id, &new_entry.time_played, &new_entry.status.unwrap()])
            .on_err(e)).next().unwrap().get(0);

        // Create library entry
        try!(trans.execute(
                "INSERT INTO Library (entry_id, login_id) VALUES ($1, $2)",
                &[&entry_id, &user_id])
            .on_err(e));

        try!(trans.commit().on_err(e));
    }

    Ok(Response::with((status::Ok, Json(new_entry))))
}

fn post_login(req: &mut Request) -> IronResult<Response> {
    let login = try!(req.get::<bodyparser::Struct<Login>>()
                     .on_err("bad request")).unwrap();

    let db = req.db();
    try!(db.execute(
            "INSERT INTO Login (username, password, email) \
            VALUES ($1, $2, $3)",
            &[&login.username,&login.password,&login.email]).on_err(
                "failed, probably because name/email already exists"));

    Ok(Response::with((status::Ok, Json(login))))
}

fn get_status(req: &mut Request) -> IronResult<Response> {
    let e = status::InternalServerError;

    let db = req.db();
    let stmt = try!(db.prepare("SELECT unnest(enum_range(NULL::Status))").on_err(e));
    let res = try!(stmt.query(&[]).on_err(e)).map(|x| {
        x.get(0)
    }).collect::<Vec<Status>>();

    Ok(Response::with((status::Ok, Json(res))))
}

fn get_game_by_id(req: &mut Request) -> IronResult<Response> {
    let e = status::NoContent;
    // TODO: Create an extension method to do this prettier
    let id: i32 = try!(req.extensions.find::<Router>()
                       .and_then(|x| x.find("id"))
                       .ok_or(LibError)
                       .and_then(|x| FromStr::from_str(x).map_err(|_| LibError))
                       .on_err(e));

    let db = req.db();
    let stmt = try!(db.prepare("SELECT * FROM Game WHERE id = $1").on_err(e));
    let mut res = try!(stmt.query(&[&id]).on_err(("Oops", e))).collect_sql::<Vec<Game>>();

    if res.is_empty() {
        Ok(Response::with(e))
    } else {
        Ok(Response::with((status::Ok, Json(res.pop()))))
    }
}

fn get_games(req: &mut Request) -> IronResult<Response> {
    let db = req.db();
    let e = ("Database error!", status::InternalServerError);

    let stmt = try!(db.prepare("SELECT * FROM Game").on_err(e));
    let res = try!(stmt.query(&[]).on_err(e)).collect_sql::<Vec<Game>>();

    Ok(Response::with((status::Ok, Json(res))))
}

fn get_entry(req: &mut Request) -> IronResult<Response> {
    let e = status::NoContent;
    let user_id: i32 = try!(req.extensions.find::<Router>()
                            .and_then(|x| x.find("uid"))
                            .ok_or(LibError)
                            .and_then(|x| FromStr::from_str(x).map_err(|_| LibError))
                            .on_err(e));
    let entry_id: i32 = try!(req.extensions.find::<Router>()
                             .and_then(|x| x.find("eid"))
                             .ok_or(LibError)
                             .and_then(|x| FromStr::from_str(x).map_err(|_| LibError))
                             .on_err(e));

    let db = req.db();
    let stmt = try!(db.prepare(
            "SELECT e.* FROM Login lo JOIN Library li ON lo.id = li.login_id \
            JOIN Entry e ON e.id = li.entry_id WHERE e.id = $1 AND lo.id = $2").on_err(e));
    let mut res = try!(stmt.query(&[&entry_id, &user_id]).on_err(("Oops", e)))
        .collect_sql::<Vec<Entry>>();

    if res.is_empty() {
        Ok(Response::with(e))
    } else {
        Ok(Response::with((status::Ok, Json(res.pop()))))
    }
}

fn get_library(req: &mut Request) -> IronResult<Response> {
    let e = status::InternalServerError;
    let user_id: i32 = try!(req.extensions.find::<Router>()
                            .and_then(|x| x.find("uid"))
                            .ok_or(LibError)
                            .and_then(|x| FromStr::from_str(x).map_err(|_| LibError))
                            .on_err(e));

    let db = req.db();
    let stmt = try!(db.prepare(
            "SELECT e.* FROM Login lo JOIN Library li ON lo.id = li.login_id \
            JOIN Entry e ON e.id = li.entry_id WHERE lo.id = $1").on_err(e));
    let res = try!(stmt.query(&[&user_id]).on_err(("Oops", e)))
        .collect_sql::<Vec<Entry>>();

    Ok(Response::with((status::Ok, Json(res))))
}

fn get_user_by_id(req: &mut Request) -> IronResult<Response> {
    let e = status::NoContent;
    let id: i32 = try!(req.extensions.find::<Router>()
                       .and_then(|x| x.find("id"))
                       .ok_or(LibError)
                       .and_then(|x| FromStr::from_str(x).map_err(|_| LibError))
                       .on_err(e));

    let db = req.db();
    let stmt = try!(db.prepare("SELECT * FROM Login WHERE id = $1").on_err(e));
    let mut res = try!(stmt.query(&[&id]).on_err(e)).collect_sql::<Vec<User>>();

    if res.is_empty() {
        Ok(Response::with(e))
    } else {
        Ok(Response::with((status::Ok, Json(res.pop()))))
    }
}

fn get_users(req: &mut Request) -> IronResult<Response> {
    let db = req.db();
    let e = ("Database error!", status::InternalServerError);

    let stmt = try!(db.prepare("SELECT * FROM Login").on_err(e));
    let res = try!(stmt.query(&[]).on_err(e)).collect_sql::<Vec<User>>();

    Ok(Response::with((status::Ok, Json(res))))
}
<|endoftext|>


<commit_before>
extern crate mprovision;
#[macro_use(version)]
extern crate version;
extern crate chrono;
#[cfg(test)]
#[macro_use(expect)]
extern crate expectest;
extern crate clap;

use mprovision as mp;
use std::env;
use cli::{Error, Result, Command};

mod cli;

fn main() {
    match cli::parse(env::args()) {
        Ok(_) => println!("Ok"),
        Err(e) => e.exit(),
    }
}
// fn search(args: &::docopt::ArgvMap) -> CliResult {
//    let text = args.get_str("<text>");
//    if text.is_empty() {
//        return Err(CliError::EmptyParameter("<text>"));
//    }
//
//    let info = mp::with_dir(directory(args), |dir| mp::search(dir, text))?;
//    if info.profiles.is_empty() {
//        println!("Nothing found for '{}'", text);
//    } else {
//        for profile in &info.profiles {
//            println!("\n{}", profile.description());
//        }
//        println!("\nFound {} of {}", info.profiles.len(), info.total);
//    }
//    Ok(())
// }
//
// fn remove(args: &::docopt::ArgvMap) -> CliResult {
//    let uuid = args.get_str("<uuid>");
//    if uuid.is_empty() {
//        return Err(CliError::EmptyParameter("<uuid>"));
//    }
//
//    mp::with_dir(directory(args), |dir| mp::remove(dir, uuid))?;
//    println!("'{}' was removed", uuid);
//    Ok(())
// }
//
// fn show_xml(args: &::docopt::ArgvMap) -> CliResult {
//    let uuid = args.get_str("<uuid>");
//    if uuid.is_empty() {
//        return Err(CliError::EmptyParameter("<uuid>"));
//    }
//
//    let xml = mp::with_dir(directory(args), |dir| mp::xml(dir, uuid))?;
//    println!("{}", xml);
//    Ok(())
// }
//
// fn show_expired(args: &::docopt::ArgvMap) -> CliResult {
//    use chrono::*;
//
//    let mut date = UTC::now();
//    if let Some(days) = args.get_str("<days>").parse::<i64>().ok() {
//        if days < 0 || days > 365 {
//            return Err(CliError::Custom("<days> should be between 0 and 365".to_string()));
//        }
//        date = date + Duration::days(days);
//    }
//
//    let info = mp::with_dir(directory(args), |dir| mp::expired_profiles(dir, date))?;
//    if info.profiles.is_empty() {
//        println!("All provisioning profiles are valid");
//    } else {
//        for profile in &info.profiles {
//            println!("\n{}", profile.description());
//        }
//        println!("\nFound {} of {}", info.profiles.len(), info.total);
//    }
//    Ok(())
// }
//
// fn remove_expired(args: &::docopt::ArgvMap) -> CliResult {
//    use chrono::*;
//
//    let info = mp::with_dir(directory(args), |dir| mp::expired_profiles(dir, UTC::now()))?;
//    if info.profiles.is_empty() {
//        println!("All provisioning profiles are valid");
//    } else {
//        let mut errors_counter = 0;
//        let mut removals_counter = 0;
//        for profile in info.profiles {
//            match std::fs::remove_file(&profile.path) {
//                Ok(_) => {
//                    removals_counter += 1;
//                    println!("'{}' was removed", profile.uuid)
//                }
//                Err(e) => {
//                    errors_counter += 1;
//                    println!("Error while trying to remove '{}'", profile.uuid);
//                    println!("{}", e);
//                }
//            }
//        }
//        println!("Removed {} of {}", removals_counter, info.total);
//        if errors_counter > 0 {
//            println!("Errors: {}", errors_counter);
//        }
//    }
//    Ok(())
// }
//
// fn directory(args: &::docopt::ArgvMap) -> Option<&Path> {
//    let dir_name = args.get_str("<directory>");
//    if dir_name.is_empty() {
//        None
//    } else {
//        Some(dir_name.as_ref())
//    }
// }
<commit_msg>Prepare to run commands<commit_after>
extern crate mprovision;
#[macro_use(version)]
extern crate version;
extern crate chrono;
#[cfg(test)]
#[macro_use(expect)]
extern crate expectest;
extern crate clap;

use mprovision as mp;
use std::env;

mod cli;

fn main() {
    let result = cli::parse(env::args()).and_then(|command| Ok(()));
    if let Err(e) = result {
        e.exit();
    }
}

// fn search(args: &::docopt::ArgvMap) -> CliResult {
//    let text = args.get_str("<text>");
//    if text.is_empty() {
//        return Err(CliError::EmptyParameter("<text>"));
//    }
//
//    let info = mp::with_dir(directory(args), |dir| mp::search(dir, text))?;
//    if info.profiles.is_empty() {
//        println!("Nothing found for '{}'", text);
//    } else {
//        for profile in &info.profiles {
//            println!("\n{}", profile.description());
//        }
//        println!("\nFound {} of {}", info.profiles.len(), info.total);
//    }
//    Ok(())
// }
//
// fn remove(args: &::docopt::ArgvMap) -> CliResult {
//    let uuid = args.get_str("<uuid>");
//    if uuid.is_empty() {
//        return Err(CliError::EmptyParameter("<uuid>"));
//    }
//
//    mp::with_dir(directory(args), |dir| mp::remove(dir, uuid))?;
//    println!("'{}' was removed", uuid);
//    Ok(())
// }
//
// fn show_xml(args: &::docopt::ArgvMap) -> CliResult {
//    let uuid = args.get_str("<uuid>");
//    if uuid.is_empty() {
//        return Err(CliError::EmptyParameter("<uuid>"));
//    }
//
//    let xml = mp::with_dir(directory(args), |dir| mp::xml(dir, uuid))?;
//    println!("{}", xml);
//    Ok(())
// }
//
// fn show_expired(args: &::docopt::ArgvMap) -> CliResult {
//    use chrono::*;
//
//    let mut date = UTC::now();
//    if let Some(days) = args.get_str("<days>").parse::<i64>().ok() {
//        if days < 0 || days > 365 {
//            return Err(CliError::Custom("<days> should be between 0 and 365".to_string()));
//        }
//        date = date + Duration::days(days);
//    }
//
//    let info = mp::with_dir(directory(args), |dir| mp::expired_profiles(dir, date))?;
//    if info.profiles.is_empty() {
//        println!("All provisioning profiles are valid");
//    } else {
//        for profile in &info.profiles {
//            println!("\n{}", profile.description());
//        }
//        println!("\nFound {} of {}", info.profiles.len(), info.total);
//    }
//    Ok(())
// }
//
// fn remove_expired(args: &::docopt::ArgvMap) -> CliResult {
//    use chrono::*;
//
//    let info = mp::with_dir(directory(args), |dir| mp::expired_profiles(dir, UTC::now()))?;
//    if info.profiles.is_empty() {
//        println!("All provisioning profiles are valid");
//    } else {
//        let mut errors_counter = 0;
//        let mut removals_counter = 0;
//        for profile in info.profiles {
//            match std::fs::remove_file(&profile.path) {
//                Ok(_) => {
//                    removals_counter += 1;
//                    println!("'{}' was removed", profile.uuid)
//                }
//                Err(e) => {
//                    errors_counter += 1;
//                    println!("Error while trying to remove '{}'", profile.uuid);
//                    println!("{}", e);
//                }
//            }
//        }
//        println!("Removed {} of {}", removals_counter, info.total);
//        if errors_counter > 0 {
//            println!("Errors: {}", errors_counter);
//        }
//    }
//    Ok(())
// }
//
// fn directory(args: &::docopt::ArgvMap) -> Option<&Path> {
//    let dir_name = args.get_str("<directory>");
//    if dir_name.is_empty() {
//        None
//    } else {
//        Some(dir_name.as_ref())
//    }
// }
<|endoftext|>


<commit_before>use rustc::lint::{LintArray, LateLintPass, LateContext, LintPass};
use rustc_front::hir::*;
use rustc_front::intravisit::*;
use syntax::ast::{LitKind, DUMMY_NODE_ID};
use syntax::codemap::{DUMMY_SP, dummy_spanned};
use utils::{span_lint_and_then, in_macro, snippet_opt, SpanlessEq};

/// **What it does:** This lint checks for boolean expressions that can be written more concisely
///
/// **Why is this bad?** Readability of boolean expressions suffers from unnecesessary duplication
///
/// **Known problems:** Ignores short circuting behavior, bitwise and/or and xor. Ends up suggesting things like !(a == b)
///
/// **Example:** `if a && true` should be `if a`
declare_lint! {
    pub NONMINIMAL_BOOL, Warn,
    "checks for boolean expressions that can be written more concisely"
}

/// **What it does:** This lint checks for boolean expressions that contain terminals that can be eliminated
///
/// **Why is this bad?** This is most likely a logic bug
///
/// **Known problems:** Ignores short circuiting behavior
///
/// **Example:** The `b` in `if a && b || a` is unnecessary because the expression is equivalent to `if a`
declare_lint! {
    pub LOGIC_BUG, Warn,
    "checks for boolean expressions that contain terminals which can be eliminated"
}

#[derive(Copy,Clone)]
pub struct NonminimalBool;

impl LintPass for NonminimalBool {
    fn get_lints(&self) -> LintArray {
        lint_array!(NONMINIMAL_BOOL, LOGIC_BUG)
    }
}

impl LateLintPass for NonminimalBool {
    fn check_item(&mut self, cx: &LateContext, item: &Item) {
        NonminimalBoolVisitor(cx).visit_item(item)
    }
}

struct NonminimalBoolVisitor<'a, 'tcx: 'a>(&'a LateContext<'a, 'tcx>);

use quine_mc_cluskey::Bool;
struct Hir2Qmm<'a, 'tcx: 'a, 'v> {
    terminals: Vec<&'v Expr>,
    cx: &'a LateContext<'a, 'tcx>
}

impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> {
    fn extract(&mut self, op: BinOp_, a: &[&'v Expr], mut v: Vec<Bool>) -> Result<Vec<Bool>, String> {
        for a in a {
            if let ExprBinary(binop, ref lhs, ref rhs) = a.node {
                if binop.node == op {
                    v = self.extract(op, &[lhs, rhs], v)?;
                    continue;
                }
            }
            v.push(self.run(a)?);
        }
        Ok(v)
    }

    fn run(&mut self, e: &'v Expr) -> Result<Bool, String> {
        // prevent folding of `cfg!` macros and the like
        if !in_macro(self.cx, e.span) {
            match e.node {
                ExprUnary(UnNot, ref inner) => return Ok(Bool::Not(box self.run(inner)?)),
                ExprBinary(binop, ref lhs, ref rhs) => {
                    match binop.node {
                        BiOr => return Ok(Bool::Or(self.extract(BiOr, &[lhs, rhs], Vec::new())?)),
                        BiAnd => return Ok(Bool::And(self.extract(BiAnd, &[lhs, rhs], Vec::new())?)),
                        _ => {},
                    }
                },
                ExprLit(ref lit) => {
                    match lit.node {
                        LitKind::Bool(true) => return Ok(Bool::True),
                        LitKind::Bool(false) => return Ok(Bool::False),
                        _ => {},
                    }
                },
                _ => {},
            }
        }
        for (n, expr) in self.terminals.iter().enumerate() {
            if SpanlessEq::new(self.cx).ignore_fn().eq_expr(e, expr) {
                #[allow(cast_possible_truncation)]
                return Ok(Bool::Term(n as u8));
            }
            let negated = match e.node {
                ExprBinary(binop, ref lhs, ref rhs) => {
                    let mk_expr = |op| Expr {
                        id: DUMMY_NODE_ID,
                        span: DUMMY_SP,
                        attrs: None,
                        node: ExprBinary(dummy_spanned(op), lhs.clone(), rhs.clone()),
                    };
                    match binop.node {
                        BiEq => mk_expr(BiNe),
                        BiNe => mk_expr(BiEq),
                        BiGt => mk_expr(BiLe),
                        BiGe => mk_expr(BiLt),
                        BiLt => mk_expr(BiGe),
                        BiLe => mk_expr(BiGt),
                        _ => continue,
                    }
                },
                _ => continue,
            };
            if SpanlessEq::new(self.cx).ignore_fn().eq_expr(&negated, expr) {
                #[allow(cast_possible_truncation)]
                return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
            }
        }
        let n = self.terminals.len();
        self.terminals.push(e);
        if n < 32 {
            #[allow(cast_possible_truncation)]
            Ok(Bool::Term(n as u8))
        } else {
            Err("too many literals".to_owned())
        }
    }
}

fn suggest(cx: &LateContext, suggestion: &Bool, terminals: &[&Expr]) -> String {
    fn recurse(brackets: bool, cx: &LateContext, suggestion: &Bool, terminals: &[&Expr], mut s: String) -> String {
        use quine_mc_cluskey::Bool::*;
        let snip = |e: &Expr| snippet_opt(cx, e.span).expect("don't try to improve booleans created by macros");
        match *suggestion {
            True => {
                s.push_str("true");
                s
            },
            False => {
                s.push_str("false");
                s
            },
            Not(ref inner) => {
                match **inner {
                    And(_) | Or(_) => {
                        s.push('!');
                        recurse(true, cx, inner, terminals, s)
                    },
                    Term(n) => {
                        match terminals[n as usize].node {
                            ExprBinary(binop, ref lhs, ref rhs) => {
                                let op = match binop.node {
                                    BiEq => " != ",
                                    BiNe => " == ",
                                    BiLt => " >= ",
                                    BiGt => " <= ",
                                    BiLe => " > ",
                                    BiGe => " < ",
                                    _ => {
                                        s.push('!');
                                        return recurse(true, cx, inner, terminals, s)
                                    },
                                };
                                s.push_str(&snip(lhs));
                                s.push_str(op);
                                s.push_str(&snip(rhs));
                                s
                            },
                            _ => {
                                s.push('!');
                                recurse(false, cx, inner, terminals, s)
                            },
                        }
                    },
                    _ => {
                        s.push('!');
                        recurse(false, cx, inner, terminals, s)
                    },
                }
            },
            And(ref v) => {
                if brackets {
                    s.push('(');
                }
                if let Or(_) = v[0] {
                    s = recurse(true, cx, &v[0], terminals, s);
                } else {
                    s = recurse(false, cx, &v[0], terminals, s);
                }
                for inner in &v[1..] {
                    s.push_str(" && ");
                    if let Or(_) = *inner {
                        s = recurse(true, cx, inner, terminals, s);
                    } else {
                        s = recurse(false, cx, inner, terminals, s);
                    }
                }
                if brackets {
                    s.push(')');
                }
                s
            },
            Or(ref v) => {
                if brackets {
                    s.push('(');
                }
                s = recurse(false, cx, &v[0], terminals, s);
                for inner in &v[1..] {
                    s.push_str(" || ");
                    s = recurse(false, cx, inner, terminals, s);
                }
                if brackets {
                    s.push(')');
                }
                s
            },
            Term(n) => {
                if brackets {
                    if let ExprBinary(..) = terminals[n as usize].node {
                        s.push('(');
                    }
                }
                s.push_str(&snip(&terminals[n as usize]));
                if brackets {
                    if let ExprBinary(..) = terminals[n as usize].node {
                        s.push(')');
                    }
                }
                s
            }
        }
    }
    recurse(false, cx, suggestion, terminals, String::new())
}

fn simple_negate(b: Bool) -> Bool {
    use quine_mc_cluskey::Bool::*;
    match b {
        True => False,
        False => True,
        t @ Term(_) => Not(Box::new(t)),
        And(mut v) => {
            for el in &mut v {
                *el = simple_negate(::std::mem::replace(el, True));
            }
            Or(v)
        },
        Or(mut v) => {
            for el in &mut v {
                *el = simple_negate(::std::mem::replace(el, True));
            }
            And(v)
        },
        Not(inner) => *inner,
    }
}

#[derive(Default)]
struct Stats {
    terminals: [usize; 32],
    negations: usize,
    ops: usize,
}

fn terminal_stats(b: &Bool) -> Stats {
    fn recurse(b: &Bool, stats: &mut Stats) {
        match *b {
            True | False => stats.ops += 1,
            Not(ref inner) => {
                match **inner {
                    And(_) | Or(_) => stats.ops += 1, // brackets are also operations
                    _ => stats.negations += 1,
                }
                recurse(inner, stats);
            },
            And(ref v) | Or(ref v) => {
                stats.ops += v.len() - 1;
                for inner in v {
                    recurse(inner, stats);
                }
            },
            Term(n) => stats.terminals[n as usize] += 1,
        }
    }
    use quine_mc_cluskey::Bool::*;
    let mut stats = Stats::default();
    recurse(b, &mut stats);
    stats
}

impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
    fn bool_expr(&self, e: &Expr) {
        let mut h2q = Hir2Qmm {
            terminals: Vec::new(),
            cx: self.0,
        };
        if let Ok(expr) = h2q.run(e) {
            let stats = terminal_stats(&expr);
            let mut simplified = expr.simplify();
            for simple in Bool::Not(Box::new(expr.clone())).simplify() {
                match simple {
                    Bool::Not(_) | Bool::True | Bool::False => {},
                    _ => simplified.push(Bool::Not(Box::new(simple.clone()))),
                }
                let simple_negated = simple_negate(simple);
                if simplified.iter().any(|s| *s == simple_negated) {
                    continue;
                }
                simplified.push(simple_negated);
            }
            let mut improvements = Vec::new();
            'simplified: for suggestion in &simplified {
                let simplified_stats = terminal_stats(&suggestion);
                let mut improvement = false;
                for i in 0..32 {
                    // ignore any "simplifications" that end up requiring a terminal more often than in the original expression
                    if stats.terminals[i] < simplified_stats.terminals[i] {
                        continue 'simplified;
                    }
                    if stats.terminals[i] != 0 && simplified_stats.terminals[i] == 0 {
                        span_lint_and_then(self.0, LOGIC_BUG, e.span, "this boolean expression contains a logic bug", |db| {
                            db.span_help(h2q.terminals[i].span, "this expression can be optimized out by applying boolean operations to the outer expression");
                            db.span_suggestion(e.span, "it would look like the following", suggest(self.0, suggestion, &h2q.terminals));
                        });
                        // don't also lint `NONMINIMAL_BOOL`
                        return;
                    }
                    // if the number of occurrences of a terminal decreases or any of the stats decreases while none increases
                    improvement |= (stats.terminals[i] > simplified_stats.terminals[i]) ||
                        (stats.negations > simplified_stats.negations && stats.ops == simplified_stats.ops) ||
                        (stats.ops > simplified_stats.ops && stats.negations == simplified_stats.negations);
                }
                if improvement {
                    improvements.push(suggestion);
                }
            }
            if !improvements.is_empty() {
                span_lint_and_then(self.0, NONMINIMAL_BOOL, e.span, "this boolean expression can be simplified", |db| {
                    for suggestion in &improvements {
                        db.span_suggestion(e.span, "try", suggest(self.0, suggestion, &h2q.terminals));
                    }
                });
            }
        }
    }
}

impl<'a, 'v, 'tcx> Visitor<'v> for NonminimalBoolVisitor<'a, 'tcx> {
    fn visit_expr(&mut self, e: &'v Expr) {
        if in_macro(self.0, e.span) { return }
        match e.node {
            ExprBinary(binop, _, _) if binop.node == BiOr || binop.node == BiAnd => self.bool_expr(e),
            ExprUnary(UnNot, ref inner) => {
                if self.0.tcx.node_types()[&inner.id].is_bool() {
                    self.bool_expr(e);
                } else {
                    walk_expr(self, e);
                }
            },
            _ => walk_expr(self, e),
        }
    }
}
<commit_msg>dogfood<commit_after>use rustc::lint::{LintArray, LateLintPass, LateContext, LintPass};
use rustc_front::hir::*;
use rustc_front::intravisit::*;
use syntax::ast::{LitKind, DUMMY_NODE_ID};
use syntax::codemap::{DUMMY_SP, dummy_spanned};
use utils::{span_lint_and_then, in_macro, snippet_opt, SpanlessEq};

/// **What it does:** This lint checks for boolean expressions that can be written more concisely
///
/// **Why is this bad?** Readability of boolean expressions suffers from unnecesessary duplication
///
/// **Known problems:** Ignores short circuting behavior, bitwise and/or and xor. Ends up suggesting things like !(a == b)
///
/// **Example:** `if a && true` should be `if a`
declare_lint! {
    pub NONMINIMAL_BOOL, Warn,
    "checks for boolean expressions that can be written more concisely"
}

/// **What it does:** This lint checks for boolean expressions that contain terminals that can be eliminated
///
/// **Why is this bad?** This is most likely a logic bug
///
/// **Known problems:** Ignores short circuiting behavior
///
/// **Example:** The `b` in `if a && b || a` is unnecessary because the expression is equivalent to `if a`
declare_lint! {
    pub LOGIC_BUG, Warn,
    "checks for boolean expressions that contain terminals which can be eliminated"
}

#[derive(Copy,Clone)]
pub struct NonminimalBool;

impl LintPass for NonminimalBool {
    fn get_lints(&self) -> LintArray {
        lint_array!(NONMINIMAL_BOOL, LOGIC_BUG)
    }
}

impl LateLintPass for NonminimalBool {
    fn check_item(&mut self, cx: &LateContext, item: &Item) {
        NonminimalBoolVisitor(cx).visit_item(item)
    }
}

struct NonminimalBoolVisitor<'a, 'tcx: 'a>(&'a LateContext<'a, 'tcx>);

use quine_mc_cluskey::Bool;
struct Hir2Qmm<'a, 'tcx: 'a, 'v> {
    terminals: Vec<&'v Expr>,
    cx: &'a LateContext<'a, 'tcx>
}

impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> {
    fn extract(&mut self, op: BinOp_, a: &[&'v Expr], mut v: Vec<Bool>) -> Result<Vec<Bool>, String> {
        for a in a {
            if let ExprBinary(binop, ref lhs, ref rhs) = a.node {
                if binop.node == op {
                    v = self.extract(op, &[lhs, rhs], v)?;
                    continue;
                }
            }
            v.push(self.run(a)?);
        }
        Ok(v)
    }

    fn run(&mut self, e: &'v Expr) -> Result<Bool, String> {
        // prevent folding of `cfg!` macros and the like
        if !in_macro(self.cx, e.span) {
            match e.node {
                ExprUnary(UnNot, ref inner) => return Ok(Bool::Not(box self.run(inner)?)),
                ExprBinary(binop, ref lhs, ref rhs) => {
                    match binop.node {
                        BiOr => return Ok(Bool::Or(self.extract(BiOr, &[lhs, rhs], Vec::new())?)),
                        BiAnd => return Ok(Bool::And(self.extract(BiAnd, &[lhs, rhs], Vec::new())?)),
                        _ => {},
                    }
                },
                ExprLit(ref lit) => {
                    match lit.node {
                        LitKind::Bool(true) => return Ok(Bool::True),
                        LitKind::Bool(false) => return Ok(Bool::False),
                        _ => {},
                    }
                },
                _ => {},
            }
        }
        for (n, expr) in self.terminals.iter().enumerate() {
            if SpanlessEq::new(self.cx).ignore_fn().eq_expr(e, expr) {
                #[allow(cast_possible_truncation)]
                return Ok(Bool::Term(n as u8));
            }
            let negated = match e.node {
                ExprBinary(binop, ref lhs, ref rhs) => {
                    let mk_expr = |op| Expr {
                        id: DUMMY_NODE_ID,
                        span: DUMMY_SP,
                        attrs: None,
                        node: ExprBinary(dummy_spanned(op), lhs.clone(), rhs.clone()),
                    };
                    match binop.node {
                        BiEq => mk_expr(BiNe),
                        BiNe => mk_expr(BiEq),
                        BiGt => mk_expr(BiLe),
                        BiGe => mk_expr(BiLt),
                        BiLt => mk_expr(BiGe),
                        BiLe => mk_expr(BiGt),
                        _ => continue,
                    }
                },
                _ => continue,
            };
            if SpanlessEq::new(self.cx).ignore_fn().eq_expr(&negated, expr) {
                #[allow(cast_possible_truncation)]
                return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
            }
        }
        let n = self.terminals.len();
        self.terminals.push(e);
        if n < 32 {
            #[allow(cast_possible_truncation)]
            Ok(Bool::Term(n as u8))
        } else {
            Err("too many literals".to_owned())
        }
    }
}

fn suggest(cx: &LateContext, suggestion: &Bool, terminals: &[&Expr]) -> String {
    fn recurse(brackets: bool, cx: &LateContext, suggestion: &Bool, terminals: &[&Expr], mut s: String) -> String {
        use quine_mc_cluskey::Bool::*;
        let snip = |e: &Expr| snippet_opt(cx, e.span).expect("don't try to improve booleans created by macros");
        match *suggestion {
            True => {
                s.push_str("true");
                s
            },
            False => {
                s.push_str("false");
                s
            },
            Not(ref inner) => {
                match **inner {
                    And(_) | Or(_) => {
                        s.push('!');
                        recurse(true, cx, inner, terminals, s)
                    },
                    Term(n) => {
                        if let ExprBinary(binop, ref lhs, ref rhs) = terminals[n as usize].node {
                            let op = match binop.node {
                                BiEq => " != ",
                                BiNe => " == ",
                                BiLt => " >= ",
                                BiGt => " <= ",
                                BiLe => " > ",
                                BiGe => " < ",
                                _ => {
                                    s.push('!');
                                    return recurse(true, cx, inner, terminals, s)
                                },
                            };
                            s.push_str(&snip(lhs));
                            s.push_str(op);
                            s.push_str(&snip(rhs));
                            s
                        } else {
                            s.push('!');
                            recurse(false, cx, inner, terminals, s)
                        }
                    },
                    _ => {
                        s.push('!');
                        recurse(false, cx, inner, terminals, s)
                    },
                }
            },
            And(ref v) => {
                if brackets {
                    s.push('(');
                }
                if let Or(_) = v[0] {
                    s = recurse(true, cx, &v[0], terminals, s);
                } else {
                    s = recurse(false, cx, &v[0], terminals, s);
                }
                for inner in &v[1..] {
                    s.push_str(" && ");
                    if let Or(_) = *inner {
                        s = recurse(true, cx, inner, terminals, s);
                    } else {
                        s = recurse(false, cx, inner, terminals, s);
                    }
                }
                if brackets {
                    s.push(')');
                }
                s
            },
            Or(ref v) => {
                if brackets {
                    s.push('(');
                }
                s = recurse(false, cx, &v[0], terminals, s);
                for inner in &v[1..] {
                    s.push_str(" || ");
                    s = recurse(false, cx, inner, terminals, s);
                }
                if brackets {
                    s.push(')');
                }
                s
            },
            Term(n) => {
                if brackets {
                    if let ExprBinary(..) = terminals[n as usize].node {
                        s.push('(');
                    }
                }
                s.push_str(&snip(&terminals[n as usize]));
                if brackets {
                    if let ExprBinary(..) = terminals[n as usize].node {
                        s.push(')');
                    }
                }
                s
            }
        }
    }
    recurse(false, cx, suggestion, terminals, String::new())
}

fn simple_negate(b: Bool) -> Bool {
    use quine_mc_cluskey::Bool::*;
    match b {
        True => False,
        False => True,
        t @ Term(_) => Not(Box::new(t)),
        And(mut v) => {
            for el in &mut v {
                *el = simple_negate(::std::mem::replace(el, True));
            }
            Or(v)
        },
        Or(mut v) => {
            for el in &mut v {
                *el = simple_negate(::std::mem::replace(el, True));
            }
            And(v)
        },
        Not(inner) => *inner,
    }
}

#[derive(Default)]
struct Stats {
    terminals: [usize; 32],
    negations: usize,
    ops: usize,
}

fn terminal_stats(b: &Bool) -> Stats {
    fn recurse(b: &Bool, stats: &mut Stats) {
        match *b {
            True | False => stats.ops += 1,
            Not(ref inner) => {
                match **inner {
                    And(_) | Or(_) => stats.ops += 1, // brackets are also operations
                    _ => stats.negations += 1,
                }
                recurse(inner, stats);
            },
            And(ref v) | Or(ref v) => {
                stats.ops += v.len() - 1;
                for inner in v {
                    recurse(inner, stats);
                }
            },
            Term(n) => stats.terminals[n as usize] += 1,
        }
    }
    use quine_mc_cluskey::Bool::*;
    let mut stats = Stats::default();
    recurse(b, &mut stats);
    stats
}

impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
    fn bool_expr(&self, e: &Expr) {
        let mut h2q = Hir2Qmm {
            terminals: Vec::new(),
            cx: self.0,
        };
        if let Ok(expr) = h2q.run(e) {
            let stats = terminal_stats(&expr);
            let mut simplified = expr.simplify();
            for simple in Bool::Not(Box::new(expr.clone())).simplify() {
                match simple {
                    Bool::Not(_) | Bool::True | Bool::False => {},
                    _ => simplified.push(Bool::Not(Box::new(simple.clone()))),
                }
                let simple_negated = simple_negate(simple);
                if simplified.iter().any(|s| *s == simple_negated) {
                    continue;
                }
                simplified.push(simple_negated);
            }
            let mut improvements = Vec::new();
            'simplified: for suggestion in &simplified {
                let simplified_stats = terminal_stats(&suggestion);
                let mut improvement = false;
                for i in 0..32 {
                    // ignore any "simplifications" that end up requiring a terminal more often than in the original expression
                    if stats.terminals[i] < simplified_stats.terminals[i] {
                        continue 'simplified;
                    }
                    if stats.terminals[i] != 0 && simplified_stats.terminals[i] == 0 {
                        span_lint_and_then(self.0, LOGIC_BUG, e.span, "this boolean expression contains a logic bug", |db| {
                            db.span_help(h2q.terminals[i].span, "this expression can be optimized out by applying boolean operations to the outer expression");
                            db.span_suggestion(e.span, "it would look like the following", suggest(self.0, suggestion, &h2q.terminals));
                        });
                        // don't also lint `NONMINIMAL_BOOL`
                        return;
                    }
                    // if the number of occurrences of a terminal decreases or any of the stats decreases while none increases
                    improvement |= (stats.terminals[i] > simplified_stats.terminals[i]) ||
                        (stats.negations > simplified_stats.negations && stats.ops == simplified_stats.ops) ||
                        (stats.ops > simplified_stats.ops && stats.negations == simplified_stats.negations);
                }
                if improvement {
                    improvements.push(suggestion);
                }
            }
            if !improvements.is_empty() {
                span_lint_and_then(self.0, NONMINIMAL_BOOL, e.span, "this boolean expression can be simplified", |db| {
                    for suggestion in &improvements {
                        db.span_suggestion(e.span, "try", suggest(self.0, suggestion, &h2q.terminals));
                    }
                });
            }
        }
    }
}

impl<'a, 'v, 'tcx> Visitor<'v> for NonminimalBoolVisitor<'a, 'tcx> {
    fn visit_expr(&mut self, e: &'v Expr) {
        if in_macro(self.0, e.span) { return }
        match e.node {
            ExprBinary(binop, _, _) if binop.node == BiOr || binop.node == BiAnd => self.bool_expr(e),
            ExprUnary(UnNot, ref inner) => {
                if self.0.tcx.node_types()[&inner.id].is_bool() {
                    self.bool_expr(e);
                } else {
                    walk_expr(self, e);
                }
            },
            _ => walk_expr(self, e),
        }
    }
}
<|endoftext|>


<commit_before>// Extern crates.
use piston::*;
use piston::game_window::keycode;
use graphics::*;

// Local crate.
use action;
use settings;
use object;
use Object = object::Object;
use text;
use game_state;

pub struct SnakeApp {
    // Tells where the surface is.
    surface_y: Option<f64>,
    game_state: Option<game_state::GameState>,
    camera_pos: Option<[f64, ..2]>,
    camera_follow_percentage: Option<f64>,
    player_index: Option<uint>,
    blood_bar_index: Option<uint>,
    air_bar_index: Option<uint>,
    // Contains the game objects.
    objects: Vec<Object>,
}

// use following line to switch to GLFW back-end
//impl Game<GameWindowGLFW> for SnakeApp
impl Game<GameWindowSDL2> for SnakeApp {
    fn render(&self, c: &Context, gl: &mut Gl) {
        // Get camera coordinates.
        let (cam_x, cam_y) = if self.camera_pos.is_some() {
                let camera_pos = self.camera_pos.unwrap();
                (camera_pos[0], camera_pos[1] + 0.4)
            } else { (0.0, 0.0) };

        // Render surface.
        let surface_y = self.surface_y.unwrap();
        c.rect(-1.0, surface_y - cam_y, 2.0, 0.05).color(settings::BLUE).fill(gl);

        // Render objects in layers.
        let cam = &c.trans(-cam_x, -cam_y);
        for i in range(0u, settings::NUMBER_OF_LAYERS) {
            for obj in self.objects.iter() {
                if obj.layer == i { obj.render(cam, c, gl); }
            }
        }

        let text_c = c.flip_v_local();
        let text_c = text_c.zoom(0.0025);
        match self.game_state.unwrap() {
            game_state::Win => {
                let pos = settings::YOU_WIN_POS;
                text::text(settings::YOU_WIN_TEXT,
                    &text_c
                    .trans(pos[0], pos[1])
                    .color(settings::YOU_WIN_TEXT_COLOR)
                , gl);
            },
            game_state::Loose => {
                let pos = settings::YOU_LOOSE_POS;
                text::text(settings::YOU_LOOSE_TEXT,
                    &text_c
                    .trans(pos[0], pos[1])
                    .color(settings::YOU_LOOSE_TEXT_COLOR)
                , gl);
            },
            game_state::Play => {

            },
        }

        // TEST
        // text::text("restart", &c.flip_v_local().zoom(0.001).color(settings::BLACK), gl);
    }

    fn update(&mut self, dt: f64, _asset_store: &mut AssetStore) {
        self.update_objects(dt);
        self.fill_air();
        self.win();
        self.loose();
        self.show_blood();
        self.show_air();
        self.follow_player(dt);
    }

    fn load(&mut self, _asset_store: &mut AssetStore) {
        self.camera_follow_percentage = Some(settings::CAMERA_FOLLOW_PERCENTAGE);
        self.camera_pos = Some(settings::INITIAL_CAMERA_POS);
        self.surface_y = Some(settings::SURFACE_Y);
        self.game_state = Some(settings::INITIAL_GAME_STATE);

        // Add player.
        self.objects.push(Object::player(
            settings::ORIGIN,
            settings::BLUE,
            settings::PLAYER_INITIAL_BLOOD,
            settings::PLAYER_INITIAL_AIR,
            [settings::PLAYER_ACCELERATION_LEFT, settings::PLAYER_ACCELERATION_RIGHT],
            [settings::PLAYER_ACCELERATION_UP, settings::PLAYER_ACCELERATION_DOWN]
        ));
        self.objects.push(Object::bar_background());
        self.player_index = Some(0);

        // Add blood and air bar.
        self.add_bars();

        // Add air bottles.
        self.add_air_bottles();

        // Add snakes.
        self.add_snakes();
    }

    fn key_press(&mut self, key: keycode::KeyCode, _asset_store: &mut AssetStore) {
        // TEST
        // println!("Key pressed {}", key);

        if self.game_state.unwrap() != game_state::Play { return; }

        match (key, self.player_index) {
            (keycode::RightKey, Some(player_index)) => {
                self.objects.get_mut(player_index).move_right();
            },
            (keycode::UpKey, Some(player_index)) => {
                self.objects.get_mut(player_index).move_up();
            },
            (keycode::LeftKey, Some(player_index)) => {
                self.objects.get_mut(player_index).move_left();
            },
            (keycode::DownKey, Some(player_index)) => {
                self.objects.get_mut(player_index).move_down();
            },
            _ => {},
        }
    }

    fn key_release(&mut self, key: keycode::KeyCode, asset_store: &mut AssetStore) {
        // TEST
        // println!("Key released {}", key);

        if key == keycode::EnterKey || key == keycode::SpaceKey {
            match self.game_state.unwrap() {
                game_state::Win | game_state::Loose => self.restart(asset_store),
                _ => {},
            }
        }
    }
}

impl SnakeApp {
    pub fn new() -> SnakeApp {
        SnakeApp {
            camera_pos: None,
            camera_follow_percentage: None,
            game_state: None,
            surface_y: None,
            objects: Vec::new(),
            player_index: None,
            blood_bar_index: None,
            air_bar_index: None,
        }
    }

    pub fn add_bars(&mut self) {
        self.objects.push(Object::bar(
            settings::AIR_BAR_POS,
            "air",
            settings::AIR_BAR_TEXT_COLOR,
            settings::AIR_BAR_BACKGROUND_COLOR,
            settings::AIR_BAR_BAR_COLOR,
            settings::AIR_BAR_INITIAL_VALUE
        ));
        self.air_bar_index = Some(self.objects.len() - 1);
        self.objects.push(Object::bar(
            settings::BLOOD_BAR_POS,
            "blood",
            settings::BLOOD_BAR_TEXT_COLOR,
            settings::BLOOD_BAR_BACKGROUND_COLOR,
            settings::BLOOD_BAR_BAR_COLOR,
            settings::BLOOD_BAR_INITIAL_VALUE
        ));
        self.blood_bar_index = Some(self.objects.len() - 1);
    }

    pub fn add_snakes(&mut self) {
        if settings::SNAKE_1_ADD { self.objects.push(Object::snake(settings::SNAKE_1_POS, settings::SNAKE_1_SETTINGS)); }
        if settings::SNAKE_2_ADD { self.objects.push(Object::snake(settings::SNAKE_2_POS, settings::SNAKE_2_SETTINGS)); }
    }

    pub fn add_air_bottles(&mut self) {
        let air_bottles = settings::AIR_BOTTLE_POS;
        let n = air_bottles.len() / 2;
        for i in range(0, n) {
            self.objects.push(Object::air_bottle([air_bottles[i * 2], air_bottles[i * 2 + 1]]));
        }
    }

    fn follow_player(&mut self, dt: f64) {
        if self.camera_pos.is_none() { return; }
        // Make camera follow player.
        let camera_pos = self.camera_pos.unwrap();
        let camera_follow_percentage = self.camera_follow_percentage.unwrap();
        let player_pos = self.player_pos();
        let (dx, dy) = (player_pos[0] - camera_pos[0], player_pos[1] - camera_pos[1]);
        let dx = camera_follow_percentage * dt * dx;
        let dy = camera_follow_percentage * dt * dy;
        self.camera_pos = Some([camera_pos[0] + dx, camera_pos[1] + dy]);
    }

    fn show_blood(&mut self) {
        if self.blood_bar_index == None { return; }
        // Show blood.
        let player_blood = self.player_blood();
        let blood_bar_index = self.blood_bar_index.unwrap();
        let blood_bar = self.objects.get_mut(blood_bar_index);
        match blood_bar.data {
            object::BarData(ref mut bar) => {
                bar.value = player_blood;
            },
            _ => {},
        }
    }

    fn show_air(&mut self) {
        if self.air_bar_index == None { return; }
        // Show air.
        let player_air = self.player_air();
        let air_bar_index = self.air_bar_index.unwrap();
        let air_bar = self.objects.get_mut(air_bar_index);
        match air_bar.data {
            object::BarData(ref mut bar) => {
                bar.value = player_air;
            },
            _ => {},
        }
    }

    fn win(&mut self) {
        let player_pos = self.player_pos();
        // When player reaches surface, win.
        if player_pos[1] >= self.surface_y.unwrap() {
            self.game_state = Some(game_state::Win);
            return;
        }
    }

    fn loose(&mut self) {
        let blood = self.player_blood();
        let air = self.player_air();
        if blood < 0.0 || air < 0.0 {
            self.game_state = Some(game_state::Loose);
            return;
        }
    }

    fn restart(&mut self, asset_store: &mut AssetStore) {
        *self = SnakeApp::new();
        self.load(asset_store);
    }

    fn update_objects(&mut self, dt: f64) {
        if self.game_state.unwrap() != game_state::Play { return; }

        // Update states of objects.
        let player_pos = self.player_pos();
        let mut attack_damage: f64 = 0.0;
        for obj in self.objects.mut_iter() {
            match obj.update(dt, player_pos) {
                action::Passive => {},
                action::Attack(attack) => { attack_damage += attack; },
            }
        }
        // Bite player.
        self.bite_player(attack_damage);

        // Decrease the player's air with time.
        let air = self.player_air();
        self.set_player_air(air - dt * settings::PLAYER_LOOSE_AIR_SPEED);
    }

    fn player_pos(&self) -> [f64, ..2] {
        let player_index = self.player_index.unwrap();
        self.objects.get(player_index).pos
    }

    fn player_blood(&self) -> f64 {
        let player_index = self.player_index.unwrap();
        self.objects.get(player_index).blood().unwrap()
    }

    fn player_air(&self) -> f64 {
        let player_index = self.player_index.unwrap();
        self.objects.get(player_index).air().unwrap()
    }

    fn set_player_air(&mut self, val: f64) {
        let player_index = self.player_index.unwrap();
        *self.objects.get_mut(player_index).air_mut().unwrap() = val;
    }

    fn bite_player(&mut self, damage: f64) {
        let player_index = self.player_index.unwrap();
        self.objects.get_mut(player_index).player_mut().unwrap().bite(damage);
    }

    fn fill_air(&mut self) {
        let player_pos = self.player_pos();
        let mut air = self.player_air();
        for obj in self.objects.mut_iter() {
            let pos = obj.pos;
            match obj.air_bottle_mut() {
                None => {},
                Some(air_bottle) => {
                    let dx = pos[0] - player_pos[0];
                    let dy = pos[1] - player_pos[1];
                    let d = dx * dx + dy * dy;
                    if d <= settings::AIR_BOTTLE_RADIUS * settings::AIR_BOTTLE_RADIUS {
                        air += air_bottle.fill_up;
                        air_bottle.fill_up = 0.0;
                    }
                },
            }
        }

        air = if air > 1.0 { 1.0 } else { air };
        self.set_player_air(air);
    }
}

<commit_msg>Upgraded to latest Piston<commit_after>// Extern crates.
use piston::*;
use graphics::*;

// Local crate.
use action;
use settings;
use object;
use Object = object::Object;
use text;
use game_state;

pub struct SnakeApp {
    // Tells where the surface is.
    surface_y: Option<f64>,
    game_state: Option<game_state::GameState>,
    camera_pos: Option<[f64, ..2]>,
    camera_follow_percentage: Option<f64>,
    player_index: Option<uint>,
    blood_bar_index: Option<uint>,
    air_bar_index: Option<uint>,
    // Contains the game objects.
    objects: Vec<Object>,
}

// use following line to switch to GLFW back-end
//impl Game<GameWindowGLFW> for SnakeApp
impl Game<GameWindowSDL2> for SnakeApp {
    fn render(&self, c: &Context, gl: &mut Gl) {
        // Get camera coordinates.
        let (cam_x, cam_y) = if self.camera_pos.is_some() {
                let camera_pos = self.camera_pos.unwrap();
                (camera_pos[0], camera_pos[1] + 0.4)
            } else { (0.0, 0.0) };

        // Render surface.
        let surface_y = self.surface_y.unwrap();
        c.rect(-1.0, surface_y - cam_y, 2.0, 0.05).color(settings::BLUE).fill(gl);

        // Render objects in layers.
        let cam = &c.trans(-cam_x, -cam_y);
        for i in range(0u, settings::NUMBER_OF_LAYERS) {
            for obj in self.objects.iter() {
                if obj.layer == i { obj.render(cam, c, gl); }
            }
        }

        let text_c = c.flip_v_local();
        let text_c = text_c.zoom(0.0025);
        match self.game_state.unwrap() {
            game_state::Win => {
                let pos = settings::YOU_WIN_POS;
                text::text(settings::YOU_WIN_TEXT,
                    &text_c
                    .trans(pos[0], pos[1])
                    .color(settings::YOU_WIN_TEXT_COLOR)
                , gl);
            },
            game_state::Loose => {
                let pos = settings::YOU_LOOSE_POS;
                text::text(settings::YOU_LOOSE_TEXT,
                    &text_c
                    .trans(pos[0], pos[1])
                    .color(settings::YOU_LOOSE_TEXT_COLOR)
                , gl);
            },
            game_state::Play => {

            },
        }

        // TEST
        // text::text("restart", &c.flip_v_local().zoom(0.001).color(settings::BLACK), gl);
    }

    fn update(&mut self, dt: f64, _asset_store: &mut AssetStore) {
        self.update_objects(dt);
        self.fill_air();
        self.win();
        self.loose();
        self.show_blood();
        self.show_air();
        self.follow_player(dt);
    }

    fn load(&mut self, _asset_store: &mut AssetStore) {
        self.camera_follow_percentage = Some(settings::CAMERA_FOLLOW_PERCENTAGE);
        self.camera_pos = Some(settings::INITIAL_CAMERA_POS);
        self.surface_y = Some(settings::SURFACE_Y);
        self.game_state = Some(settings::INITIAL_GAME_STATE);

        // Add player.
        self.objects.push(Object::player(
            settings::ORIGIN,
            settings::BLUE,
            settings::PLAYER_INITIAL_BLOOD,
            settings::PLAYER_INITIAL_AIR,
            [settings::PLAYER_ACCELERATION_LEFT, settings::PLAYER_ACCELERATION_RIGHT],
            [settings::PLAYER_ACCELERATION_UP, settings::PLAYER_ACCELERATION_DOWN]
        ));
        self.objects.push(Object::bar_background());
        self.player_index = Some(0);

        // Add blood and air bar.
        self.add_bars();

        // Add air bottles.
        self.add_air_bottles();

        // Add snakes.
        self.add_snakes();
    }

    fn key_press(&mut self, key: keyboard::Key, _asset_store: &mut AssetStore) {
        // TEST
        // println!("Key pressed {}", key);

        if self.game_state.unwrap() != game_state::Play { return; }

        match (key, self.player_index) {
            (keyboard::Right, Some(player_index)) => {
                self.objects.get_mut(player_index).move_right();
            },
            (keyboard::Up, Some(player_index)) => {
                self.objects.get_mut(player_index).move_up();
            },
            (keyboard::Left, Some(player_index)) => {
                self.objects.get_mut(player_index).move_left();
            },
            (keyboard::Down, Some(player_index)) => {
                self.objects.get_mut(player_index).move_down();
            },
            _ => {},
        }
    }

    fn key_release(&mut self, key: keyboard::Key, asset_store: &mut AssetStore) {
        // TEST
        // println!("Key released {}", key);

        if key == keyboard::Enter || key == keyboard::Space {
            match self.game_state.unwrap() {
                game_state::Win | game_state::Loose => self.restart(asset_store),
                _ => {},
            }
        }
    }
}

impl SnakeApp {
    pub fn new() -> SnakeApp {
        SnakeApp {
            camera_pos: None,
            camera_follow_percentage: None,
            game_state: None,
            surface_y: None,
            objects: Vec::new(),
            player_index: None,
            blood_bar_index: None,
            air_bar_index: None,
        }
    }

    pub fn add_bars(&mut self) {
        self.objects.push(Object::bar(
            settings::AIR_BAR_POS,
            "air",
            settings::AIR_BAR_TEXT_COLOR,
            settings::AIR_BAR_BACKGROUND_COLOR,
            settings::AIR_BAR_BAR_COLOR,
            settings::AIR_BAR_INITIAL_VALUE
        ));
        self.air_bar_index = Some(self.objects.len() - 1);
        self.objects.push(Object::bar(
            settings::BLOOD_BAR_POS,
            "blood",
            settings::BLOOD_BAR_TEXT_COLOR,
            settings::BLOOD_BAR_BACKGROUND_COLOR,
            settings::BLOOD_BAR_BAR_COLOR,
            settings::BLOOD_BAR_INITIAL_VALUE
        ));
        self.blood_bar_index = Some(self.objects.len() - 1);
    }

    pub fn add_snakes(&mut self) {
        if settings::SNAKE_1_ADD { self.objects.push(Object::snake(settings::SNAKE_1_POS, settings::SNAKE_1_SETTINGS)); }
        if settings::SNAKE_2_ADD { self.objects.push(Object::snake(settings::SNAKE_2_POS, settings::SNAKE_2_SETTINGS)); }
    }

    pub fn add_air_bottles(&mut self) {
        let air_bottles = settings::AIR_BOTTLE_POS;
        let n = air_bottles.len() / 2;
        for i in range(0, n) {
            self.objects.push(Object::air_bottle([air_bottles[i * 2], air_bottles[i * 2 + 1]]));
        }
    }

    fn follow_player(&mut self, dt: f64) {
        if self.camera_pos.is_none() { return; }
        // Make camera follow player.
        let camera_pos = self.camera_pos.unwrap();
        let camera_follow_percentage = self.camera_follow_percentage.unwrap();
        let player_pos = self.player_pos();
        let (dx, dy) = (player_pos[0] - camera_pos[0], player_pos[1] - camera_pos[1]);
        let dx = camera_follow_percentage * dt * dx;
        let dy = camera_follow_percentage * dt * dy;
        self.camera_pos = Some([camera_pos[0] + dx, camera_pos[1] + dy]);
    }

    fn show_blood(&mut self) {
        if self.blood_bar_index == None { return; }
        // Show blood.
        let player_blood = self.player_blood();
        let blood_bar_index = self.blood_bar_index.unwrap();
        let blood_bar = self.objects.get_mut(blood_bar_index);
        match blood_bar.data {
            object::BarData(ref mut bar) => {
                bar.value = player_blood;
            },
            _ => {},
        }
    }

    fn show_air(&mut self) {
        if self.air_bar_index == None { return; }
        // Show air.
        let player_air = self.player_air();
        let air_bar_index = self.air_bar_index.unwrap();
        let air_bar = self.objects.get_mut(air_bar_index);
        match air_bar.data {
            object::BarData(ref mut bar) => {
                bar.value = player_air;
            },
            _ => {},
        }
    }

    fn win(&mut self) {
        let player_pos = self.player_pos();
        // When player reaches surface, win.
        if player_pos[1] >= self.surface_y.unwrap() {
            self.game_state = Some(game_state::Win);
            return;
        }
    }

    fn loose(&mut self) {
        let blood = self.player_blood();
        let air = self.player_air();
        if blood < 0.0 || air < 0.0 {
            self.game_state = Some(game_state::Loose);
            return;
        }
    }

    fn restart(&mut self, asset_store: &mut AssetStore) {
        *self = SnakeApp::new();
        self.load(asset_store);
    }

    fn update_objects(&mut self, dt: f64) {
        if self.game_state.unwrap() != game_state::Play { return; }

        // Update states of objects.
        let player_pos = self.player_pos();
        let mut attack_damage: f64 = 0.0;
        for obj in self.objects.mut_iter() {
            match obj.update(dt, player_pos) {
                action::Passive => {},
                action::Attack(attack) => { attack_damage += attack; },
            }
        }
        // Bite player.
        self.bite_player(attack_damage);

        // Decrease the player's air with time.
        let air = self.player_air();
        self.set_player_air(air - dt * settings::PLAYER_LOOSE_AIR_SPEED);
    }

    fn player_pos(&self) -> [f64, ..2] {
        let player_index = self.player_index.unwrap();
        self.objects.get(player_index).pos
    }

    fn player_blood(&self) -> f64 {
        let player_index = self.player_index.unwrap();
        self.objects.get(player_index).blood().unwrap()
    }

    fn player_air(&self) -> f64 {
        let player_index = self.player_index.unwrap();
        self.objects.get(player_index).air().unwrap()
    }

    fn set_player_air(&mut self, val: f64) {
        let player_index = self.player_index.unwrap();
        *self.objects.get_mut(player_index).air_mut().unwrap() = val;
    }

    fn bite_player(&mut self, damage: f64) {
        let player_index = self.player_index.unwrap();
        self.objects.get_mut(player_index).player_mut().unwrap().bite(damage);
    }

    fn fill_air(&mut self) {
        let player_pos = self.player_pos();
        let mut air = self.player_air();
        for obj in self.objects.mut_iter() {
            let pos = obj.pos;
            match obj.air_bottle_mut() {
                None => {},
                Some(air_bottle) => {
                    let dx = pos[0] - player_pos[0];
                    let dy = pos[1] - player_pos[1];
                    let d = dx * dx + dy * dy;
                    if d <= settings::AIR_BOTTLE_RADIUS * settings::AIR_BOTTLE_RADIUS {
                        air += air_bottle.fill_up;
                        air_bottle.fill_up = 0.0;
                    }
                },
            }
        }

        air = if air > 1.0 { 1.0 } else { air };
        self.set_player_air(air);
    }
}

<|endoftext|>


<commit_before>use std::string::String;

use super::sql::*;
use super::rel::*;
use super::schema::*;

pub struct SqlToRel {
    default_schema: Option<String>
}

impl SqlToRel {

    pub fn sql_to_rel(&self, sql: &ASTNode, tt: &TupleType) -> Box<Rel> {
        match sql {
            &ASTNode::SQLSelect { ref projection, ref relation, .. } => {

                let expr : Vec<Rex> = projection.iter()
                    .map(|e| self.sql_to_rex(&e, tt))
                    .collect();

                let input = match relation {
                    &Some(ref r) => Some(self.sql_to_rel(r, tt)),
                    &None => None
                };

                Box::new(Rel::Projection {
                    expr: expr,
                    input: input
                })
            },

            _ => panic!("not implemented")
        }
    }

    pub fn sql_to_rex(&self, sql: &ASTNode, tt: &TupleType) -> Rex {
        match sql {
            &ASTNode::SQLIdentifier { ref id, .. } => {
                match tt.columns.iter().position(|c| c.name.eq(id) ) {
                    Some(index) => Rex::TupleValue(index),
                    None => panic!("Invalid identifier")
                }
            },
            _ => panic!("not implemented")
        }
    }

}
<commit_msg>cleaning up some error handling<commit_after>use std::string::String;

use super::sql::*;
use super::rel::*;
use super::schema::*;

pub struct SqlToRel {
    default_schema: Option<String>
}

impl SqlToRel {

    pub fn sql_to_rel(&self, sql: &ASTNode, tt: &TupleType) -> Result<Box<Rel>, String> {
        match sql {
            &ASTNode::SQLSelect { ref projection, ref relation, .. } => {

                let expr : Vec<Rex> = projection.iter()
                    .map(|e| self.sql_to_rex(&e, tt).unwrap() )
                    .collect();

                let input = match relation {
                    &Some(ref r) => Some(self.sql_to_rel(r, tt)?),
                    &None => None
                };

                Ok(Box::new(Rel::Projection {
                    expr: expr,
                    input: input
                }))
            },

            _ => panic!("not implemented")
        }
    }

    pub fn sql_to_rex(&self, sql: &ASTNode, tt: &TupleType) -> Result<Rex, String> {
        match sql {
            &ASTNode::SQLIdentifier { ref id, .. } => {
                match tt.columns.iter().position(|c| c.name.eq(id) ) {
                    Some(index) => Ok(Rex::TupleValue(index)),
                    None => Err(String::from("Invalid identifier"))
                }
            },
            _ => Err(String::from("Unsupported ast node"))
        }
    }

}
<|endoftext|>


<commit_before>// Copyright © 2016, Canal TP and/or its affiliates. All rights reserved.
//
// This file is part of Navitia,
//     the software to build cool stuff with public transport.
//
// Hope you'll enjoy and contribute to this project,
//     powered by Canal TP (www.canaltp.fr).
// Help us simplify mobility and open public transport:
//     a non ending quest to the responsive locomotion way of traveling!
//
// LICENCE: This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU Affero General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
//
// Stay tuned using
// twitter @navitia
// IRC #navitia on freenode
// https://groups.google.com/d/forum/navitia
// www.navitia.io
use super::model;
use regex;
use rs_es;
use rs_es::query::Query as rs_q;
use rs_es::operations::search::SearchResult;
use rs_es::units as rs_u;
use mimir;
use serde_json;
use serde;

fn build_rs_client(cnx: &String) -> rs_es::Client {
    let re = regex::Regex::new(r"(?:https?://)?(?P<host>.+?):(?P<port>\d+)").unwrap();
    let cap = re.captures(&cnx).unwrap();
    let host = cap.name("host").unwrap().as_str();
    let port = cap.name("port").unwrap().as_str().parse::<u32>().unwrap();

    rs_es::Client::new(&host, port)
}

/// takes a ES json blob and build a Place from it
/// it uses the _type field of ES to know which type of the Place enum to fill
pub fn make_place(doc_type: String, value: Option<Box<serde_json::Value>>) -> Option<mimir::Place> {
    value.and_then(|v| {
        fn convert<T: serde::Deserialize>(v: serde_json::Value,
                                          f: fn(T) -> mimir::Place)
                                          -> Option<mimir::Place> {
            serde_json::from_value::<T>(v)
                .map_err(|err| warn!("Impossible to load ES result: {}", err))
                .ok()
                .map(f)
        }
        match doc_type.as_ref() {
            "addr" => convert(*v, mimir::Place::Addr),
            "street" => convert(*v, mimir::Place::Street),
            "admin" => convert(*v, mimir::Place::Admin),
            "poi" => convert(*v, mimir::Place::Poi),
            "stop" => convert(*v, mimir::Place::Stop),
            _ => {
                warn!("unknown ES return value, _type field = {}", doc_type);
                None
            }
        }
    })
}

#[derive(Debug, Eq, PartialEq)]
enum MatchType {
    Prefix,
    Fuzzy,
}

fn build_query(q: &str,
               match_type: MatchType,
               coord: &Option<model::Coord>,
               shape: Option<Vec<rs_es::units::Location>>)
               -> rs_es::query::Query {
    use rs_es::query::functions::Function;
    // we order the type of object we want
    // Note: the addresses are boosted more because even if we don't want them first
    // because they are more severely filtered
    let boost_addr = rs_q::build_term("_type", "addr").with_boost(5000).build();
    let boost_admin = rs_q::build_term("_type", "admin").with_boost(3000).build();
    let boost_stop = rs_q::build_term("_type", "stop").with_boost(2000).build();

    let main_match_type = match match_type {
        MatchType::Prefix => "label.prefix",
        MatchType::Fuzzy => "label.ngram",
    };

    let boost_main_match_query = rs_q::build_multi_match(vec![main_match_type.to_string()],
                                                         q.to_string())
        .with_boost(100)
        .build();
    let boost_zipcode_match_query = rs_q::build_multi_match(vec!["zip_codes.prefix".to_string()],
                                                            q.to_string())
        .with_boost(500)
        .build();

    let mut should_query = vec![boost_addr,
                                boost_admin,
                                boost_stop,
                                boost_main_match_query,
                                boost_zipcode_match_query];

    // for fuzzy search we also search by the prefix index (with a greater boost than ngram)
    // to have better results
    if match_type == MatchType::Fuzzy {
        should_query.push(rs_q::build_match("label.prefix", q.to_string())
            .with_boost(1000)
            .build());
    }

    if let &Some(ref c) = coord {
        // if we have coordinate, we boost we result near this coordinate
        let boost_on_proximity =
            rs_q::build_function_score()
                .with_boost_mode(rs_es::query::compound::BoostMode::Multiply)
                .with_boost(500)
                .with_function(Function::build_decay("coord",
                                           rs_u::Location::LatLon(c.lat, c.lon),
                                           rs_u::Distance::new(50f64,
                                                               rs_u::DistanceUnit::Kilometer))
                                   .build_exp())
                .build();
        should_query.push(boost_on_proximity);
    } else {
        // if we don't have coords, we take the field `weight` into account
        let boost_on_weight = rs_q::build_function_score()
            .with_boost_mode(rs_es::query::compound::BoostMode::Multiply)
            .with_boost(300)
            .with_query(rs_q::build_match_all().build())
            .with_function(Function::build_field_value_factor("weight")
                .with_factor(1)
                .with_modifier(rs_es::query::functions::Modifier::Log1p)
                .build())
            .build();
        should_query.push(boost_on_weight);
    }

    let sub_query = rs_q::build_bool()
        .with_should(should_query)
        .build();

    use rs_es::query::{CombinationMinimumShouldMatch, MinimumShouldMatch};
    let min_match = |min, threshold| {
        CombinationMinimumShouldMatch::new(MinimumShouldMatch::from(min),
                                           MinimumShouldMatch::from(threshold))
    };

    let minimum_should_match = match match_type {
        MatchType::Prefix => {
            // when we search [0, 2] fields, we need to match 100% of them
            // when we search [3, 4] fields, we need to match 90% of them
            // when we search [5, +] fields, we only need to match 70% on them
            // this way if the input has more fields than the documents
            // (additional information like the country, the region, ...)
            // they can be ignored
            MinimumShouldMatch::from(vec![min_match(2, 90f64), min_match(4, 70f64)])
        }
        // for fuzzy search we lower our expectation and we accept 50% of token match
        MatchType::Fuzzy => MinimumShouldMatch::from(50f64),
    };

    let mut must = vec![rs_q::build_multi_match(vec![main_match_type.to_string(),
                                                     "zip_codes.prefix".to_string()],
                                                q.to_string())
                            .with_minimum_should_match(minimum_should_match)
                            .build()];

    if let Some(s) = shape {
        must.push(rs_q::build_geo_polygon("coord", s).build());
    }

    // filter to handle house number
    // we either want:
    // * to exactly match the document house_number
    // * or that the document has no house_number
    let filter = rs_q::build_bool()
        .with_should(vec![rs_q::build_bool()
                              .with_must_not(rs_q::build_exists("house_number").build())
                              .build(),
                          rs_q::build_match("house_number", q.to_string()).build()])
        .with_must(must)
        .build();

    rs_q::build_bool()
        .with_must(vec![sub_query])
        .with_filter(filter)
        .build()
}

fn make_stops_index(pt_dataset: &str) -> String {
    if pt_dataset.is_empty() {
        "munin_stops".to_string()
    } else {
        format!("munin_stops_{}", pt_dataset)
    }
}

fn index_exists(client: &mut rs_es::Client, stops_index: &String) -> bool {
    match client.open_index(stops_index) {
        Ok(_) => true,
        Err(_) => {
            debug!("{} index not found", stops_index);
            false
        }
    }
}

fn make_indexes<'a>(stops_index: &'a str, client: &mut rs_es::Client) -> Vec<&'a str> {
    let mut result = vec![];
    if index_exists(client, &stops_index.to_string()) {
        result.push(stops_index);
    }
    if index_exists(client, &"munin_street_network".to_string()) {
        result.push("munin_street_network");
    }
    return result;
}

fn query(q: &str,
         pt_dataset: &str,
         cnx: &str,
         match_type: MatchType,
         offset: u64,
         limit: u64,
         coord: &Option<model::Coord>,
         shape: Option<Vec<rs_es::units::Location>>)
         -> Result<Vec<mimir::Place>, rs_es::error::EsError> {
    let query = build_query(q, match_type, coord, shape);

    let mut client = build_rs_client(&cnx.to_string());

    let stops_index = make_stops_index(pt_dataset);
    let indexes = make_indexes(&stops_index, &mut client);

    let result: SearchResult<serde_json::Value> = try!(client.search_query()
        .with_indexes(&indexes)
        .with_query(&query)
        .with_from(offset)
        .with_size(limit)
        .send());

    debug!("{} documents found", result.hits.total);

    // for the moment rs-es does not handle enum Document,
    // so we need to convert the ES glob to a Place
    Ok(result.hits
        .hits
        .into_iter()
        .filter_map(|hit| make_place(hit.doc_type, hit.source))
        .collect())
}

pub fn autocomplete(q: &str,
                    pt_dataset: &str,
                    offset: u64,
                    limit: u64,
                    coord: Option<model::Coord>,
                    cnx: &str,
                    shape: Option<Vec<(f64, f64)>>)
                    -> Result<Vec<mimir::Place>, rs_es::error::EsError> {
    fn make_shape(shape: &Option<Vec<(f64, f64)>>) -> Option<Vec<rs_es::units::Location>> {
        shape.as_ref().map(|v| v.iter().map(|&l| l.into()).collect())
    }

    // First we try a prety exact match on the prefix.
    // If there are no results then we do a new fuzzy search (matching ngrams)
    let results = try!(query(&q,
                             &pt_dataset,
                             cnx,
                             MatchType::Prefix,
                             offset,
                             limit,
                             &coord,
                             make_shape(&shape)));
    if results.is_empty() {
        query(&q,
              &pt_dataset,
              cnx,
              MatchType::Fuzzy,
              offset,
              limit,
              &coord,
              make_shape(&shape))
    } else {
        Ok(results)
    }
}
<commit_msg>rename function<commit_after>// Copyright © 2016, Canal TP and/or its affiliates. All rights reserved.
//
// This file is part of Navitia,
//     the software to build cool stuff with public transport.
//
// Hope you'll enjoy and contribute to this project,
//     powered by Canal TP (www.canaltp.fr).
// Help us simplify mobility and open public transport:
//     a non ending quest to the responsive locomotion way of traveling!
//
// LICENCE: This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU Affero General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
//
// Stay tuned using
// twitter @navitia
// IRC #navitia on freenode
// https://groups.google.com/d/forum/navitia
// www.navitia.io
use super::model;
use regex;
use rs_es;
use rs_es::query::Query as rs_q;
use rs_es::operations::search::SearchResult;
use rs_es::units as rs_u;
use mimir;
use serde_json;
use serde;

fn build_rs_client(cnx: &String) -> rs_es::Client {
    let re = regex::Regex::new(r"(?:https?://)?(?P<host>.+?):(?P<port>\d+)").unwrap();
    let cap = re.captures(&cnx).unwrap();
    let host = cap.name("host").unwrap().as_str();
    let port = cap.name("port").unwrap().as_str().parse::<u32>().unwrap();

    rs_es::Client::new(&host, port)
}

/// takes a ES json blob and build a Place from it
/// it uses the _type field of ES to know which type of the Place enum to fill
pub fn make_place(doc_type: String, value: Option<Box<serde_json::Value>>) -> Option<mimir::Place> {
    value.and_then(|v| {
        fn convert<T: serde::Deserialize>(v: serde_json::Value,
                                          f: fn(T) -> mimir::Place)
                                          -> Option<mimir::Place> {
            serde_json::from_value::<T>(v)
                .map_err(|err| warn!("Impossible to load ES result: {}", err))
                .ok()
                .map(f)
        }
        match doc_type.as_ref() {
            "addr" => convert(*v, mimir::Place::Addr),
            "street" => convert(*v, mimir::Place::Street),
            "admin" => convert(*v, mimir::Place::Admin),
            "poi" => convert(*v, mimir::Place::Poi),
            "stop" => convert(*v, mimir::Place::Stop),
            _ => {
                warn!("unknown ES return value, _type field = {}", doc_type);
                None
            }
        }
    })
}

#[derive(Debug, Eq, PartialEq)]
enum MatchType {
    Prefix,
    Fuzzy,
}

fn build_query(q: &str,
               match_type: MatchType,
               coord: &Option<model::Coord>,
               shape: Option<Vec<rs_es::units::Location>>)
               -> rs_es::query::Query {
    use rs_es::query::functions::Function;
    // we order the type of object we want
    // Note: the addresses are boosted more because even if we don't want them first
    // because they are more severely filtered
    let boost_addr = rs_q::build_term("_type", "addr").with_boost(5000).build();
    let boost_admin = rs_q::build_term("_type", "admin").with_boost(3000).build();
    let boost_stop = rs_q::build_term("_type", "stop").with_boost(2000).build();

    let main_match_type = match match_type {
        MatchType::Prefix => "label.prefix",
        MatchType::Fuzzy => "label.ngram",
    };

    let boost_main_match_query = rs_q::build_multi_match(vec![main_match_type.to_string()],
                                                         q.to_string())
        .with_boost(100)
        .build();
    let boost_zipcode_match_query = rs_q::build_multi_match(vec!["zip_codes.prefix".to_string()],
                                                            q.to_string())
        .with_boost(500)
        .build();

    let mut should_query = vec![boost_addr,
                                boost_admin,
                                boost_stop,
                                boost_main_match_query,
                                boost_zipcode_match_query];

    // for fuzzy search we also search by the prefix index (with a greater boost than ngram)
    // to have better results
    if match_type == MatchType::Fuzzy {
        should_query.push(rs_q::build_match("label.prefix", q.to_string())
            .with_boost(1000)
            .build());
    }

    if let &Some(ref c) = coord {
        // if we have coordinate, we boost we result near this coordinate
        let boost_on_proximity =
            rs_q::build_function_score()
                .with_boost_mode(rs_es::query::compound::BoostMode::Multiply)
                .with_boost(500)
                .with_function(Function::build_decay("coord",
                                           rs_u::Location::LatLon(c.lat, c.lon),
                                           rs_u::Distance::new(50f64,
                                                               rs_u::DistanceUnit::Kilometer))
                                   .build_exp())
                .build();
        should_query.push(boost_on_proximity);
    } else {
        // if we don't have coords, we take the field `weight` into account
        let boost_on_weight = rs_q::build_function_score()
            .with_boost_mode(rs_es::query::compound::BoostMode::Multiply)
            .with_boost(300)
            .with_query(rs_q::build_match_all().build())
            .with_function(Function::build_field_value_factor("weight")
                .with_factor(1)
                .with_modifier(rs_es::query::functions::Modifier::Log1p)
                .build())
            .build();
        should_query.push(boost_on_weight);
    }

    let sub_query = rs_q::build_bool()
        .with_should(should_query)
        .build();

    use rs_es::query::{CombinationMinimumShouldMatch, MinimumShouldMatch};
    let min_match = |min, threshold| {
        CombinationMinimumShouldMatch::new(MinimumShouldMatch::from(min),
                                           MinimumShouldMatch::from(threshold))
    };

    let minimum_should_match = match match_type {
        MatchType::Prefix => {
            // when we search [0, 2] fields, we need to match 100% of them
            // when we search [3, 4] fields, we need to match 90% of them
            // when we search [5, +] fields, we only need to match 70% on them
            // this way if the input has more fields than the documents
            // (additional information like the country, the region, ...)
            // they can be ignored
            MinimumShouldMatch::from(vec![min_match(2, 90f64), min_match(4, 70f64)])
        }
        // for fuzzy search we lower our expectation and we accept 50% of token match
        MatchType::Fuzzy => MinimumShouldMatch::from(50f64),
    };

    let mut must = vec![rs_q::build_multi_match(vec![main_match_type.to_string(),
                                                     "zip_codes.prefix".to_string()],
                                                q.to_string())
                            .with_minimum_should_match(minimum_should_match)
                            .build()];

    if let Some(s) = shape {
        must.push(rs_q::build_geo_polygon("coord", s).build());
    }

    // filter to handle house number
    // we either want:
    // * to exactly match the document house_number
    // * or that the document has no house_number
    let filter = rs_q::build_bool()
        .with_should(vec![rs_q::build_bool()
                              .with_must_not(rs_q::build_exists("house_number").build())
                              .build(),
                          rs_q::build_match("house_number", q.to_string()).build()])
        .with_must(must)
        .build();

    rs_q::build_bool()
        .with_must(vec![sub_query])
        .with_filter(filter)
        .build()
}

fn make_stops_index(pt_dataset: &str) -> String {
    if pt_dataset.is_empty() {
        "munin_stops".to_string()
    } else {
        format!("munin_stops_{}", pt_dataset)
    }
}

fn is_existing_index(client: &mut rs_es::Client, stops_index: &String) -> bool {
    match client.open_index(stops_index) {
        Ok(_) => true,
        Err(_) => {
            debug!("{} index not found", stops_index);
            false
        }
    }
}

fn make_indexes<'a>(stops_index: &'a str, client: &mut rs_es::Client) -> Vec<&'a str> {
    let mut result = vec![];
    if is_existing_index(client, &stops_index.to_string()) {
        result.push(stops_index);
    }
    if is_existing_index(client, &"munin_street_network".to_string()) {
        result.push("munin_street_network");
    }
    return result;
}

fn query(q: &str,
         pt_dataset: &str,
         cnx: &str,
         match_type: MatchType,
         offset: u64,
         limit: u64,
         coord: &Option<model::Coord>,
         shape: Option<Vec<rs_es::units::Location>>)
         -> Result<Vec<mimir::Place>, rs_es::error::EsError> {
    let query = build_query(q, match_type, coord, shape);

    let mut client = build_rs_client(&cnx.to_string());

    let stops_index = make_stops_index(pt_dataset);
    let indexes = make_indexes(&stops_index, &mut client);

    let result: SearchResult<serde_json::Value> = try!(client.search_query()
        .with_indexes(&indexes)
        .with_query(&query)
        .with_from(offset)
        .with_size(limit)
        .send());

    debug!("{} documents found", result.hits.total);

    // for the moment rs-es does not handle enum Document,
    // so we need to convert the ES glob to a Place
    Ok(result.hits
        .hits
        .into_iter()
        .filter_map(|hit| make_place(hit.doc_type, hit.source))
        .collect())
}

pub fn autocomplete(q: &str,
                    pt_dataset: &str,
                    offset: u64,
                    limit: u64,
                    coord: Option<model::Coord>,
                    cnx: &str,
                    shape: Option<Vec<(f64, f64)>>)
                    -> Result<Vec<mimir::Place>, rs_es::error::EsError> {
    fn make_shape(shape: &Option<Vec<(f64, f64)>>) -> Option<Vec<rs_es::units::Location>> {
        shape.as_ref().map(|v| v.iter().map(|&l| l.into()).collect())
    }

    // First we try a prety exact match on the prefix.
    // If there are no results then we do a new fuzzy search (matching ngrams)
    let results = try!(query(&q,
                             &pt_dataset,
                             cnx,
                             MatchType::Prefix,
                             offset,
                             limit,
                             &coord,
                             make_shape(&shape)));
    if results.is_empty() {
        query(&q,
              &pt_dataset,
              cnx,
              MatchType::Fuzzy,
              offset,
              limit,
              &coord,
              make_shape(&shape))
    } else {
        Ok(results)
    }
}
<|endoftext|>