<commit_before>//! Simple daemon
//!
//! This daemon will start web server, track new packages and build them

use std::{env, thread};
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::time::Duration;
use std::path::PathBuf;
use time;
use crate::{
    docbuilder::RustwideBuilder,
    DocBuilderOptions,
    DocBuilder,
    utils::{update_release_activity, github_updater, pubsubhubbub},
    db::{connect_db, update_search_index},
};

#[cfg(not(target_os = "windows"))]
use ::{
    libc::fork,
    std::process::exit,
    std::fs::File,
    std::io::Write
};

pub fn start_daemon(background: bool) {
    // first check required environment variables
    for v in ["CRATESFYI_PREFIX",
              "CRATESFYI_PREFIX",
              "CRATESFYI_GITHUB_USERNAME",
              "CRATESFYI_GITHUB_ACCESSTOKEN"]
        .iter() {
        env::var(v).expect(&format!("Environment variable {} not found", v));
    }

    let dbopts = opts();

    // check paths once
    dbopts.check_paths().unwrap();

    if background {
        #[cfg(target_os = "windows")]
        {
            panic!("running in background not supported on windows");
        }
        #[cfg(not(target_os = "windows"))]
        {
            // fork the process
            let pid = unsafe { fork() };
            if pid > 0 {
                let mut file = File::create(dbopts.prefix.join("cratesfyi.pid"))
                    .expect("Failed to create pid file");
                writeln!(&mut file, "{}", pid).expect("Failed to write pid");

                info!("cratesfyi {} daemon started on: {}", ::BUILD_VERSION, pid);
                exit(0);
            }
        }
    }

    // check new crates every minute
    thread::Builder::new().name("crates.io reader".to_string()).spawn(move || {
        // space this out to prevent it from clashing against the queue-builder thread on launch
        thread::sleep(Duration::from_secs(30));
        loop {
            let opts = opts();
            let mut doc_builder = DocBuilder::new(opts);

            if doc_builder.is_locked() {
                debug!("Lock file exists, skipping checking new crates");
            } else {
                debug!("Checking new crates");
                match doc_builder.get_new_crates() {
                    Ok(n) => debug!("{} crates added to queue", n),
                    Err(e) => error!("Failed to get new crates: {}", e),
                }
            }

            thread::sleep(Duration::from_secs(60));
        }
    }).unwrap();

    // build new crates every minute
    thread::Builder::new().name("build queue reader".to_string()).spawn(move || {
        let opts = opts();
        let mut doc_builder = DocBuilder::new(opts);

        /// Represents the current state of the builder thread.
        enum BuilderState {
            /// The builder thread has just started, and hasn't built any crates yet.
            Fresh,
            /// The builder has just seen an empty build queue.
            EmptyQueue,
            /// The builder has just seen the lock file.
            Locked,
            /// The builder has just finished building a crate. The enclosed count is the number of
            /// crates built since the caches have been refreshed.
            QueueInProgress(usize),
        }

        let mut builder = RustwideBuilder::init().unwrap();

        let mut status = BuilderState::Fresh;

        loop {
            if !status.is_in_progress() {
                thread::sleep(Duration::from_secs(60));
            }

            // check lock file
            if doc_builder.is_locked() {
                warn!("Lock file exits, skipping building new crates");
                status = BuilderState::Locked;
                continue;
            }

            if status.count() >= 10 {
                // periodically, we need to flush our caches and ping the hubs
                debug!("10 builds in a row; flushing caches");
                status = BuilderState::QueueInProgress(0);

                match pubsubhubbub::ping_hubs() {
                    Err(e) => error!("Failed to ping hub: {}", e),
                    Ok(n) => debug!("Succesfully pinged {} hubs", n)
                }

                if let Err(e) = doc_builder.load_cache() {
                    error!("Failed to load cache: {}", e);
                }

                if let Err(e) = doc_builder.save_cache() {
                    error!("Failed to save cache: {}", e);
                }
            }

            // Only build crates if there are any to build
            debug!("Checking build queue");
            match doc_builder.get_queue_count() {
                Err(e) => {
                    error!("Failed to read the number of crates in the queue: {}", e);
                    continue;
                }
                Ok(0) => {
                    if status.count() > 0 {
                        // ping the hubs before continuing
                        match pubsubhubbub::ping_hubs() {
                            Err(e) => error!("Failed to ping hub: {}", e),
                            Ok(n) => debug!("Succesfully pinged {} hubs", n)
                        }

                        if let Err(e) = doc_builder.save_cache() {
                            error!("Failed to save cache: {}", e);
                        }
                    }
                    debug!("Queue is empty, going back to sleep");
                    status = BuilderState::EmptyQueue;
                    continue;
                }
                Ok(queue_count) => {
                    info!("Starting build with {} crates in queue (currently on a {} crate streak)",
                          queue_count, status.count());
                }
            }

            // if we're starting a new batch, reload our caches and sources
            if !status.is_in_progress() {
                if let Err(e) = doc_builder.load_cache() {
                    error!("Failed to load cache: {}", e);
                    continue;
                }
            }

            // Run build_packages_queue under `catch_unwind` to catch panics
            // This only panicked twice in the last 6 months but its just a better
            // idea to do this.
            let res = catch_unwind(AssertUnwindSafe(|| {
                match doc_builder.build_next_queue_package(&mut builder) {
                    Err(e) => error!("Failed to build crate from queue: {}", e),
                    Ok(crate_built) => if crate_built {
                        status.increment();
                    }

                }
            }));

            if let Err(e) = res {
                error!("GRAVE ERROR Building new crates panicked: {:?}", e);
            }
        }

        impl BuilderState {
            fn count(&self) -> usize {
                match *self {
                    BuilderState::QueueInProgress(n) => n,
                    _ => 0,
                }
            }

            fn is_in_progress(&self) -> bool {
                match *self {
                    BuilderState::QueueInProgress(_) => true,
                    _ => false,
                }
            }

            fn increment(&mut self) {
                *self = BuilderState::QueueInProgress(self.count() + 1);
            }
        }
    }).unwrap();

    // update release activity everyday at 23:55
    thread::Builder::new().name("release activity updater".to_string()).spawn(move || {
        loop {
            thread::sleep(Duration::from_secs(60));
            let now = time::now();
            if now.tm_hour == 23 && now.tm_min == 55 {
                info!("Updating release activity");
                if let Err(e) = update_release_activity() {
                    error!("Failed to update release activity: {}", e);
                }
            }
        }
    }).unwrap();

    // update search index every 3 hours
    thread::Builder::new().name("search index updater".to_string()).spawn(move || {
        loop {
            thread::sleep(Duration::from_secs(60 * 60 * 3));
            let conn = connect_db().expect("Failed to connect database");
            if let Err(e) = update_search_index(&conn) {
                error!("Failed to update search index: {}", e);
            }
        }
    }).unwrap();

    // update github stats every 6 hours
    thread::Builder::new().name("github stat updater".to_string()).spawn(move || {
        loop {
            thread::sleep(Duration::from_secs(60 * 60 * 6));
            if let Err(e) = github_updater() {
                error!("Failed to update github fields: {}", e);
            }
        }
    }).unwrap();

    // TODO: update ssl certificate every 3 months

    // at least start web server
    info!("Starting web server");
    crate::Server::start(None);
}

fn opts() -> DocBuilderOptions {
    let prefix = PathBuf::from(env::var("CRATESFYI_PREFIX")
        .expect("CRATESFYI_PREFIX environment variable not found"));
    DocBuilderOptions::from_prefix(prefix)
}
<commit_msg>Missed one<commit_after>//! Simple daemon
//!
//! This daemon will start web server, track new packages and build them

use std::{env, thread};
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::time::Duration;
use std::path::PathBuf;
use time;
use crate::{
    docbuilder::RustwideBuilder,
    DocBuilderOptions,
    DocBuilder,
    utils::{update_release_activity, github_updater, pubsubhubbub},
    db::{connect_db, update_search_index},
};

#[cfg(not(target_os = "windows"))]
use ::{
    libc::fork,
    std::process::exit,
    std::fs::File,
    std::io::Write
};

pub fn start_daemon(background: bool) {
    // first check required environment variables
    for v in ["CRATESFYI_PREFIX",
              "CRATESFYI_PREFIX",
              "CRATESFYI_GITHUB_USERNAME",
              "CRATESFYI_GITHUB_ACCESSTOKEN"]
        .iter() {
        env::var(v).expect(&format!("Environment variable {} not found", v));
    }

    let dbopts = opts();

    // check paths once
    dbopts.check_paths().unwrap();

    if background {
        #[cfg(target_os = "windows")]
        {
            panic!("running in background not supported on windows");
        }
        #[cfg(not(target_os = "windows"))]
        {
            // fork the process
            let pid = unsafe { fork() };
            if pid > 0 {
                let mut file = File::create(dbopts.prefix.join("cratesfyi.pid"))
                    .expect("Failed to create pid file");
                writeln!(&mut file, "{}", pid).expect("Failed to write pid");

                info!("cratesfyi {} daemon started on: {}", crate::BUILD_VERSION, pid);
                exit(0);
            }
        }
    }

    // check new crates every minute
    thread::Builder::new().name("crates.io reader".to_string()).spawn(move || {
        // space this out to prevent it from clashing against the queue-builder thread on launch
        thread::sleep(Duration::from_secs(30));
        loop {
            let opts = opts();
            let mut doc_builder = DocBuilder::new(opts);

            if doc_builder.is_locked() {
                debug!("Lock file exists, skipping checking new crates");
            } else {
                debug!("Checking new crates");
                match doc_builder.get_new_crates() {
                    Ok(n) => debug!("{} crates added to queue", n),
                    Err(e) => error!("Failed to get new crates: {}", e),
                }
            }

            thread::sleep(Duration::from_secs(60));
        }
    }).unwrap();

    // build new crates every minute
    thread::Builder::new().name("build queue reader".to_string()).spawn(move || {
        let opts = opts();
        let mut doc_builder = DocBuilder::new(opts);

        /// Represents the current state of the builder thread.
        enum BuilderState {
            /// The builder thread has just started, and hasn't built any crates yet.
            Fresh,
            /// The builder has just seen an empty build queue.
            EmptyQueue,
            /// The builder has just seen the lock file.
            Locked,
            /// The builder has just finished building a crate. The enclosed count is the number of
            /// crates built since the caches have been refreshed.
            QueueInProgress(usize),
        }

        let mut builder = RustwideBuilder::init().unwrap();

        let mut status = BuilderState::Fresh;

        loop {
            if !status.is_in_progress() {
                thread::sleep(Duration::from_secs(60));
            }

            // check lock file
            if doc_builder.is_locked() {
                warn!("Lock file exits, skipping building new crates");
                status = BuilderState::Locked;
                continue;
            }

            if status.count() >= 10 {
                // periodically, we need to flush our caches and ping the hubs
                debug!("10 builds in a row; flushing caches");
                status = BuilderState::QueueInProgress(0);

                match pubsubhubbub::ping_hubs() {
                    Err(e) => error!("Failed to ping hub: {}", e),
                    Ok(n) => debug!("Succesfully pinged {} hubs", n)
                }

                if let Err(e) = doc_builder.load_cache() {
                    error!("Failed to load cache: {}", e);
                }

                if let Err(e) = doc_builder.save_cache() {
                    error!("Failed to save cache: {}", e);
                }
            }

            // Only build crates if there are any to build
            debug!("Checking build queue");
            match doc_builder.get_queue_count() {
                Err(e) => {
                    error!("Failed to read the number of crates in the queue: {}", e);
                    continue;
                }
                Ok(0) => {
                    if status.count() > 0 {
                        // ping the hubs before continuing
                        match pubsubhubbub::ping_hubs() {
                            Err(e) => error!("Failed to ping hub: {}", e),
                            Ok(n) => debug!("Succesfully pinged {} hubs", n)
                        }

                        if let Err(e) = doc_builder.save_cache() {
                            error!("Failed to save cache: {}", e);
                        }
                    }
                    debug!("Queue is empty, going back to sleep");
                    status = BuilderState::EmptyQueue;
                    continue;
                }
                Ok(queue_count) => {
                    info!("Starting build with {} crates in queue (currently on a {} crate streak)",
                          queue_count, status.count());
                }
            }

            // if we're starting a new batch, reload our caches and sources
            if !status.is_in_progress() {
                if let Err(e) = doc_builder.load_cache() {
                    error!("Failed to load cache: {}", e);
                    continue;
                }
            }

            // Run build_packages_queue under `catch_unwind` to catch panics
            // This only panicked twice in the last 6 months but its just a better
            // idea to do this.
            let res = catch_unwind(AssertUnwindSafe(|| {
                match doc_builder.build_next_queue_package(&mut builder) {
                    Err(e) => error!("Failed to build crate from queue: {}", e),
                    Ok(crate_built) => if crate_built {
                        status.increment();
                    }

                }
            }));

            if let Err(e) = res {
                error!("GRAVE ERROR Building new crates panicked: {:?}", e);
            }
        }

        impl BuilderState {
            fn count(&self) -> usize {
                match *self {
                    BuilderState::QueueInProgress(n) => n,
                    _ => 0,
                }
            }

            fn is_in_progress(&self) -> bool {
                match *self {
                    BuilderState::QueueInProgress(_) => true,
                    _ => false,
                }
            }

            fn increment(&mut self) {
                *self = BuilderState::QueueInProgress(self.count() + 1);
            }
        }
    }).unwrap();

    // update release activity everyday at 23:55
    thread::Builder::new().name("release activity updater".to_string()).spawn(move || {
        loop {
            thread::sleep(Duration::from_secs(60));
            let now = time::now();
            if now.tm_hour == 23 && now.tm_min == 55 {
                info!("Updating release activity");
                if let Err(e) = update_release_activity() {
                    error!("Failed to update release activity: {}", e);
                }
            }
        }
    }).unwrap();

    // update search index every 3 hours
    thread::Builder::new().name("search index updater".to_string()).spawn(move || {
        loop {
            thread::sleep(Duration::from_secs(60 * 60 * 3));
            let conn = connect_db().expect("Failed to connect database");
            if let Err(e) = update_search_index(&conn) {
                error!("Failed to update search index: {}", e);
            }
        }
    }).unwrap();

    // update github stats every 6 hours
    thread::Builder::new().name("github stat updater".to_string()).spawn(move || {
        loop {
            thread::sleep(Duration::from_secs(60 * 60 * 6));
            if let Err(e) = github_updater() {
                error!("Failed to update github fields: {}", e);
            }
        }
    }).unwrap();

    // TODO: update ssl certificate every 3 months

    // at least start web server
    info!("Starting web server");
    crate::Server::start(None);
}

fn opts() -> DocBuilderOptions {
    let prefix = PathBuf::from(env::var("CRATESFYI_PREFIX")
        .expect("CRATESFYI_PREFIX environment variable not found"));
    DocBuilderOptions::from_prefix(prefix)
}
<|endoftext|>


<commit_before>//! Releases web handlers

use super::{duration_to_str, match_version};
use super::error::Nope;
use super::page::Page;
use super::pool::Pool;
use iron::prelude::*;
use iron::status;
use router::Router;
use rustc_serialize::json::{Json, ToJson};
use std::collections::BTreeMap;
use time;
use postgres::Connection;

/// Number of release in home page
const RELEASES_IN_HOME: i64 = 15;
/// Releases in /releases page
const RELEASES_IN_RELEASES: i64 = 30;

pub struct Release {
    name: String,
    version: String,
    description: Option<String>,
    target_name: Option<String>,
    rustdoc_status: bool,
    release_time: time::Timespec,
    stars: i32,
}

impl Default for Release {
    fn default() -> Release {
        Release {
            name: String::new(),
            version: String::new(),
            description: None,
            target_name: None,
            rustdoc_status: false,
            release_time: time::get_time(),
            stars: 0,
        }
    }
}

impl ToJson for Release {
    fn to_json(&self) -> Json {
        let mut m: BTreeMap<String, Json> = BTreeMap::new();
        m.insert("name".to_string(), self.name.to_json());
        m.insert("version".to_string(), self.version.to_json());
        m.insert("description".to_string(), self.description.to_json());
        m.insert("target_name".to_string(), self.target_name.to_json());
        m.insert("rustdoc_status".to_string(), self.rustdoc_status.to_json());
        m.insert("release_time".to_string(),
                 duration_to_str(self.release_time).to_json());
        m.insert("stars".to_string(), self.stars.to_json());
        m.to_json()
    }
}

enum Order {
    ReleaseTime, // this is default order
    GithubStars,
}

fn get_releases(conn: &Connection, page: i64, limit: i64, order: Order) -> Vec<Release> {

    let offset = (page - 1) * limit;

    // TODO: This function changed so much during development and current version have code
    //       repeats for queries. There is definitely room for improvements.
    let query = match order {
        Order::ReleaseTime => {
            "SELECT crates.name, \
                                      releases.version, \
                                      releases.description, \
                                      releases.target_name, \
                                      releases.release_time, \
                                      releases.rustdoc_status, \
                                      crates.github_stars \
                               FROM crates \
                               INNER JOIN releases ON crates.id = releases.crate_id \
                               ORDER BY releases.release_time DESC \
                               LIMIT $1 OFFSET $2"
        }
        Order::GithubStars => {
            "SELECT crates.name, \
                                      releases.version, \
                                      releases.description, \
                                      releases.target_name, \
                                      releases.release_time, \
                                      releases.rustdoc_status, \
                                      crates.github_stars \
                               FROM crates \
                               INNER JOIN releases ON releases.id = crates.latest_version_id \
                               ORDER BY crates.github_stars DESC \
                               LIMIT $1 OFFSET $2"
        }
    };

    let mut packages = Vec::new();
    for row in &conn.query(&query, &[&limit, &offset]).unwrap() {
        let package = Release {
            name: row.get(0),
            version: row.get(1),
            description: row.get(2),
            target_name: row.get(3),
            release_time: row.get(4),
            rustdoc_status: row.get(5),
            stars: row.get(6),
        };

        packages.push(package);
    }

    packages
}

fn get_releases_by_author(conn: &Connection,
                          page: i64,
                          limit: i64,
                          author: &str)
                          -> (String, Vec<Release>) {

    let offset = (page - 1) * limit;

    let query = "SELECT crates.name,
                        releases.version,
                        releases.description,
                        releases.target_name,
                        releases.release_time,
                        releases.rustdoc_status,
                        crates.github_stars,
                        authors.name
                 FROM crates
                 INNER JOIN releases ON releases.id = crates.latest_version_id
                 INNER JOIN author_rels ON releases.id = author_rels.rid
                 INNER JOIN authors ON authors.id = author_rels.aid
                 WHERE authors.slug = $1
                 ORDER BY crates.github_stars DESC
                 LIMIT $2 OFFSET $3";

    let mut author_name = String::new();
    let mut packages = Vec::new();
    for row in &conn.query(&query, &[&author, &limit, &offset]).unwrap() {
        let package = Release {
            name: row.get(0),
            version: row.get(1),
            description: row.get(2),
            target_name: row.get(3),
            release_time: row.get(4),
            rustdoc_status: row.get(5),
            stars: row.get(6),
        };

        author_name = row.get(7);
        packages.push(package);
    }

    (author_name, packages)
}

fn get_search_results(conn: &Connection,
                      query: &str,
                      page: i64,
                      limit: i64)
                      -> Option<(i64, Vec<Release>)> {

    let offset = (page - 1) * limit;
    let mut packages = Vec::new();

    for row in &conn.query("SELECT crates.name, \
                                   releases.version, \
                                   releases.description, \
                                   releases.target_name, \
                                   releases.release_time, \
                                   releases.rustdoc_status, \
                                   ts_rank_cd(crates.content, to_tsquery($1)) AS rank \
                            FROM crates \
                            INNER JOIN releases ON crates.latest_version_id = releases.id \
                            WHERE crates.content @@ to_tsquery($1) \
                            ORDER BY rank DESC \
                            LIMIT $2 OFFSET $3",
               &[&query, &limit, &offset])
        .unwrap() {

        let package = Release {
            name: row.get(0),
            version: row.get(1),
            description: row.get(2),
            target_name: row.get(3),
            release_time: row.get(4),
            rustdoc_status: row.get(5),
            ..Release::default()
        };

        packages.push(package);
    }

    if !packages.is_empty() {
        // get count of total results
        let rows = conn.query("SELECT COUNT(*) FROM crates WHERE content @@ to_tsquery($1)",
                   &[&query])
            .unwrap();

        Some((rows.get(0).get(0), packages))
    } else {
        None
    }
}

pub fn home_page(req: &mut Request) -> IronResult<Response> {
    let conn = req.extensions.get::<Pool>().unwrap();
    let packages = get_releases(conn, 1, RELEASES_IN_HOME, Order::ReleaseTime);
    Page::new(packages)
        .set_true("show_search_form")
        .set_true("hide_package_navigation")
        .to_resp("releases")
}

pub fn releases_handler(req: &mut Request) -> IronResult<Response> {
    // page number of releases
    let page_number: i64 = req.extensions
        .get::<Router>()
        .unwrap()
        .find("page")
        .unwrap_or("1")
        .parse()
        .unwrap_or(1);

    let conn = req.extensions.get::<Pool>().unwrap();
    let packages = get_releases(conn, page_number, RELEASES_IN_RELEASES, Order::ReleaseTime);

    if packages.is_empty() {
        return Err(IronError::new(Nope::CrateNotFound, status::NotFound));
    }

    // Show next and previous page buttons
    // This is a temporary solution to avoid expensive COUNT(*)
    let (show_next_page, show_previous_page) = (packages.len() == RELEASES_IN_RELEASES as usize,
                                                page_number != 1);

    Page::new(packages)
        .title("Releases")
        .set("description", "Recently uploaded crates")
        .set("release_type", "recent")
        .set_true("show_releases_navigation")
        .set_true("releases_navigation_recent_tab")
        .set_bool("show_next_page_button", show_next_page)
        .set_int("next_page", page_number + 1)
        .set_bool("show_previous_page_button", show_previous_page)
        .set_int("previous_page", page_number - 1)
        .to_resp("releases")
}

// TODO: This function is almost identical to previous one
pub fn stars_handler(req: &mut Request) -> IronResult<Response> {
    // page number of releases
    let page_number: i64 = req.extensions
        .get::<Router>()
        .unwrap()
        .find("page")
        .unwrap_or("1")
        .parse()
        .unwrap_or(1);

    let conn = req.extensions.get::<Pool>().unwrap();
    let packages = get_releases(conn, page_number, RELEASES_IN_RELEASES, Order::GithubStars);

    if packages.is_empty() {
        return Err(IronError::new(Nope::CrateNotFound, status::NotFound));
    }

    // Show next and previous page buttons
    // This is a temporary solution to avoid expensive COUNT(*)
    let (show_next_page, show_previous_page) = (packages.len() == RELEASES_IN_RELEASES as usize,
                                                page_number != 1);

    Page::new(packages)
        .title("Releases")
        .set("description", "Most starred crates")
        .set("release_type", "stars")
        .set_true("show_releases_navigation")
        .set_true("releases_navigation_stars_tab")
        .set_true("show_stars")
        .set_bool("show_next_page_button", show_next_page)
        .set_int("next_page", page_number + 1)
        .set_bool("show_previous_page_button", show_previous_page)
        .set_int("previous_page", page_number - 1)
        .to_resp("releases")
}

pub fn author_handler(req: &mut Request) -> IronResult<Response> {
    // page number of releases
    let page_number: i64 = req.extensions
        .get::<Router>()
        .unwrap()
        .find("page")
        .unwrap_or("1")
        .parse()
        .unwrap_or(1);

    let conn = req.extensions.get::<Pool>().unwrap();
    let author = req.extensions.get::<Router>().unwrap().find("author");

    if author.is_none() {
        return Err(IronError::new(Nope::CrateNotFound, status::NotFound));
    }

    let (author_name, packages) =
        get_releases_by_author(conn, page_number, RELEASES_IN_RELEASES, author.unwrap());

    if packages.is_empty() {
        return Err(IronError::new(Nope::CrateNotFound, status::NotFound));
    }

    // Show next and previous page buttons
    // This is a temporary solution to avoid expensive COUNT(*)
    let (show_next_page, show_previous_page) = (packages.len() == RELEASES_IN_RELEASES as usize,
                                                page_number != 1);

    Page::new(packages)
        .title("Releases")
        .set("description", &format!("Crates from {}", author_name))
        .set("author", &author_name)
        .set("release_type", &author.unwrap())
        .set_true("show_releases_navigation")
        .set_true("show_stars")
        .set_bool("show_next_page_button", show_next_page)
        .set_int("next_page", page_number + 1)
        .set_bool("show_previous_page_button", show_previous_page)
        .set_int("previous_page", page_number - 1)
        .to_resp("releases")
}

pub fn search_handler(req: &mut Request) -> IronResult<Response> {
    use params::{Params, Value};

    let params = req.get::<Params>().unwrap();
    let query = params.find(&["query"]);

    let conn = req.extensions.get::<Pool>().unwrap();
    if let Some(&Value::String(ref query)) = query {

        // check if I am feeling lucky button pressed and redirect user to crate page
        // if there is a match
        // TODO: Redirecting to latest doc might be more useful
        if params.find(&["i-am-feeling-lucky"]).is_some() {
            if let Some(version) = match_version(&conn, &query, None) {
                use iron::Url;
                use iron::modifiers::Redirect;
                let url = Url::parse(&format!("{}://{}:{}/crate/{}/{}",
                                              req.url.scheme,
                                              req.url.host,
                                              req.url.port,
                                              query,
                                              version)[..])
                    .unwrap();
                let mut resp = Response::with((status::Found, Redirect(url)));

                use iron::headers::{Expires, HttpDate};
                use time;
                resp.headers.set(Expires(HttpDate(time::now())));
                return Ok(resp);
            }
        }

        let search_query = query.replace(" ", " & ");
        get_search_results(&conn, &search_query, 1, RELEASES_IN_RELEASES)
            .ok_or(IronError::new(Nope::NoResults, status::NotFound))
            .and_then(|(_, results)| {
                // FIXME: There is no pagination
                Page::new(results)
                    .set("search_query", &query)
                    .title(&format!("Search results for '{}'", query))
                    .to_resp("releases")
            })
    } else {
        Err(IronError::new(Nope::NoResults, status::NotFound))
    }
}

pub fn activity_handler(req: &mut Request) -> IronResult<Response> {
    let conn = req.extensions.get::<Pool>().unwrap();
    let release_activity_data: Json =
        conn.query("SELECT value FROM config WHERE name = 'release_activity'",
                   &[])
            .unwrap()
            .get(0)
            .get(0);
    Page::new(release_activity_data)
        .title("Releases")
        .set("description", "Monthly release activity")
        .set_true("show_releases_navigation")
        .set_true("releases_navigation_activity_tab")
        .set_true("javascript_highchartjs")
        .to_resp("releases_activity")
}
<commit_msg>Update behavior of "I'm feeling lucky" button<commit_after>//! Releases web handlers

use super::{duration_to_str, match_version};
use super::error::Nope;
use super::page::Page;
use super::pool::Pool;
use iron::prelude::*;
use iron::status;
use router::Router;
use rustc_serialize::json::{Json, ToJson};
use std::collections::BTreeMap;
use time;
use postgres::Connection;

/// Number of release in home page
const RELEASES_IN_HOME: i64 = 15;
/// Releases in /releases page
const RELEASES_IN_RELEASES: i64 = 30;

pub struct Release {
    name: String,
    version: String,
    description: Option<String>,
    target_name: Option<String>,
    rustdoc_status: bool,
    release_time: time::Timespec,
    stars: i32,
}

impl Default for Release {
    fn default() -> Release {
        Release {
            name: String::new(),
            version: String::new(),
            description: None,
            target_name: None,
            rustdoc_status: false,
            release_time: time::get_time(),
            stars: 0,
        }
    }
}

impl ToJson for Release {
    fn to_json(&self) -> Json {
        let mut m: BTreeMap<String, Json> = BTreeMap::new();
        m.insert("name".to_string(), self.name.to_json());
        m.insert("version".to_string(), self.version.to_json());
        m.insert("description".to_string(), self.description.to_json());
        m.insert("target_name".to_string(), self.target_name.to_json());
        m.insert("rustdoc_status".to_string(), self.rustdoc_status.to_json());
        m.insert("release_time".to_string(),
                 duration_to_str(self.release_time).to_json());
        m.insert("stars".to_string(), self.stars.to_json());
        m.to_json()
    }
}

enum Order {
    ReleaseTime, // this is default order
    GithubStars,
}

fn get_releases(conn: &Connection, page: i64, limit: i64, order: Order) -> Vec<Release> {

    let offset = (page - 1) * limit;

    // TODO: This function changed so much during development and current version have code
    //       repeats for queries. There is definitely room for improvements.
    let query = match order {
        Order::ReleaseTime => {
            "SELECT crates.name, \
                                      releases.version, \
                                      releases.description, \
                                      releases.target_name, \
                                      releases.release_time, \
                                      releases.rustdoc_status, \
                                      crates.github_stars \
                               FROM crates \
                               INNER JOIN releases ON crates.id = releases.crate_id \
                               ORDER BY releases.release_time DESC \
                               LIMIT $1 OFFSET $2"
        }
        Order::GithubStars => {
            "SELECT crates.name, \
                                      releases.version, \
                                      releases.description, \
                                      releases.target_name, \
                                      releases.release_time, \
                                      releases.rustdoc_status, \
                                      crates.github_stars \
                               FROM crates \
                               INNER JOIN releases ON releases.id = crates.latest_version_id \
                               ORDER BY crates.github_stars DESC \
                               LIMIT $1 OFFSET $2"
        }
    };

    let mut packages = Vec::new();
    for row in &conn.query(&query, &[&limit, &offset]).unwrap() {
        let package = Release {
            name: row.get(0),
            version: row.get(1),
            description: row.get(2),
            target_name: row.get(3),
            release_time: row.get(4),
            rustdoc_status: row.get(5),
            stars: row.get(6),
        };

        packages.push(package);
    }

    packages
}

fn get_releases_by_author(conn: &Connection,
                          page: i64,
                          limit: i64,
                          author: &str)
                          -> (String, Vec<Release>) {

    let offset = (page - 1) * limit;

    let query = "SELECT crates.name,
                        releases.version,
                        releases.description,
                        releases.target_name,
                        releases.release_time,
                        releases.rustdoc_status,
                        crates.github_stars,
                        authors.name
                 FROM crates
                 INNER JOIN releases ON releases.id = crates.latest_version_id
                 INNER JOIN author_rels ON releases.id = author_rels.rid
                 INNER JOIN authors ON authors.id = author_rels.aid
                 WHERE authors.slug = $1
                 ORDER BY crates.github_stars DESC
                 LIMIT $2 OFFSET $3";

    let mut author_name = String::new();
    let mut packages = Vec::new();
    for row in &conn.query(&query, &[&author, &limit, &offset]).unwrap() {
        let package = Release {
            name: row.get(0),
            version: row.get(1),
            description: row.get(2),
            target_name: row.get(3),
            release_time: row.get(4),
            rustdoc_status: row.get(5),
            stars: row.get(6),
        };

        author_name = row.get(7);
        packages.push(package);
    }

    (author_name, packages)
}

fn get_search_results(conn: &Connection,
                      query: &str,
                      page: i64,
                      limit: i64)
                      -> Option<(i64, Vec<Release>)> {

    let offset = (page - 1) * limit;
    let mut packages = Vec::new();

    for row in &conn.query("SELECT crates.name, \
                                   releases.version, \
                                   releases.description, \
                                   releases.target_name, \
                                   releases.release_time, \
                                   releases.rustdoc_status, \
                                   ts_rank_cd(crates.content, to_tsquery($1)) AS rank \
                            FROM crates \
                            INNER JOIN releases ON crates.latest_version_id = releases.id \
                            WHERE crates.content @@ to_tsquery($1) \
                            ORDER BY rank DESC \
                            LIMIT $2 OFFSET $3",
               &[&query, &limit, &offset])
        .unwrap() {

        let package = Release {
            name: row.get(0),
            version: row.get(1),
            description: row.get(2),
            target_name: row.get(3),
            release_time: row.get(4),
            rustdoc_status: row.get(5),
            ..Release::default()
        };

        packages.push(package);
    }

    if !packages.is_empty() {
        // get count of total results
        let rows = conn.query("SELECT COUNT(*) FROM crates WHERE content @@ to_tsquery($1)",
                   &[&query])
            .unwrap();

        Some((rows.get(0).get(0), packages))
    } else {
        None
    }
}

pub fn home_page(req: &mut Request) -> IronResult<Response> {
    let conn = req.extensions.get::<Pool>().unwrap();
    let packages = get_releases(conn, 1, RELEASES_IN_HOME, Order::ReleaseTime);
    Page::new(packages)
        .set_true("show_search_form")
        .set_true("hide_package_navigation")
        .to_resp("releases")
}

pub fn releases_handler(req: &mut Request) -> IronResult<Response> {
    // page number of releases
    let page_number: i64 = req.extensions
        .get::<Router>()
        .unwrap()
        .find("page")
        .unwrap_or("1")
        .parse()
        .unwrap_or(1);

    let conn = req.extensions.get::<Pool>().unwrap();
    let packages = get_releases(conn, page_number, RELEASES_IN_RELEASES, Order::ReleaseTime);

    if packages.is_empty() {
        return Err(IronError::new(Nope::CrateNotFound, status::NotFound));
    }

    // Show next and previous page buttons
    // This is a temporary solution to avoid expensive COUNT(*)
    let (show_next_page, show_previous_page) = (packages.len() == RELEASES_IN_RELEASES as usize,
                                                page_number != 1);

    Page::new(packages)
        .title("Releases")
        .set("description", "Recently uploaded crates")
        .set("release_type", "recent")
        .set_true("show_releases_navigation")
        .set_true("releases_navigation_recent_tab")
        .set_bool("show_next_page_button", show_next_page)
        .set_int("next_page", page_number + 1)
        .set_bool("show_previous_page_button", show_previous_page)
        .set_int("previous_page", page_number - 1)
        .to_resp("releases")
}

// TODO: This function is almost identical to previous one
pub fn stars_handler(req: &mut Request) -> IronResult<Response> {
    // page number of releases
    let page_number: i64 = req.extensions
        .get::<Router>()
        .unwrap()
        .find("page")
        .unwrap_or("1")
        .parse()
        .unwrap_or(1);

    let conn = req.extensions.get::<Pool>().unwrap();
    let packages = get_releases(conn, page_number, RELEASES_IN_RELEASES, Order::GithubStars);

    if packages.is_empty() {
        return Err(IronError::new(Nope::CrateNotFound, status::NotFound));
    }

    // Show next and previous page buttons
    // This is a temporary solution to avoid expensive COUNT(*)
    let (show_next_page, show_previous_page) = (packages.len() == RELEASES_IN_RELEASES as usize,
                                                page_number != 1);

    Page::new(packages)
        .title("Releases")
        .set("description", "Most starred crates")
        .set("release_type", "stars")
        .set_true("show_releases_navigation")
        .set_true("releases_navigation_stars_tab")
        .set_true("show_stars")
        .set_bool("show_next_page_button", show_next_page)
        .set_int("next_page", page_number + 1)
        .set_bool("show_previous_page_button", show_previous_page)
        .set_int("previous_page", page_number - 1)
        .to_resp("releases")
}

pub fn author_handler(req: &mut Request) -> IronResult<Response> {
    // page number of releases
    let page_number: i64 = req.extensions
        .get::<Router>()
        .unwrap()
        .find("page")
        .unwrap_or("1")
        .parse()
        .unwrap_or(1);

    let conn = req.extensions.get::<Pool>().unwrap();
    let author = req.extensions.get::<Router>().unwrap().find("author");

    if author.is_none() {
        return Err(IronError::new(Nope::CrateNotFound, status::NotFound));
    }

    let (author_name, packages) =
        get_releases_by_author(conn, page_number, RELEASES_IN_RELEASES, author.unwrap());

    if packages.is_empty() {
        return Err(IronError::new(Nope::CrateNotFound, status::NotFound));
    }

    // Show next and previous page buttons
    // This is a temporary solution to avoid expensive COUNT(*)
    let (show_next_page, show_previous_page) = (packages.len() == RELEASES_IN_RELEASES as usize,
                                                page_number != 1);

    Page::new(packages)
        .title("Releases")
        .set("description", &format!("Crates from {}", author_name))
        .set("author", &author_name)
        .set("release_type", &author.unwrap())
        .set_true("show_releases_navigation")
        .set_true("show_stars")
        .set_bool("show_next_page_button", show_next_page)
        .set_int("next_page", page_number + 1)
        .set_bool("show_previous_page_button", show_previous_page)
        .set_int("previous_page", page_number - 1)
        .to_resp("releases")
}

pub fn search_handler(req: &mut Request) -> IronResult<Response> {
    use params::{Params, Value};

    let params = req.get::<Params>().unwrap();
    let query = params.find(&["query"]);

    let conn = req.extensions.get::<Pool>().unwrap();
    if let Some(&Value::String(ref query)) = query {

        // check if I am feeling lucky button pressed and redirect user to crate page
        // if there is a match
        // TODO: Redirecting to latest doc might be more useful
        if params.find(&["i-am-feeling-lucky"]).is_some() {

            use iron::Url;
            use iron::modifiers::Redirect;

            // redirect to a random crate if query is empty
            if query.is_empty() {
                let rows =  conn.query("SELECT crates.name, \
                                               releases.version, \
                                               releases.target_name \
                                        FROM crates \
                                        INNER JOIN releases
                                              ON crates.latest_version_id = releases.id \
                                        WHERE github_stars >= 100 AND rustdoc_status = true \
                                        OFFSET FLOOR(RANDOM() * 280) LIMIT 1", &[]).unwrap();
                                        //                ~~~~~~^
                                        // FIXME: This is a fast query but using a constant
                                        //        There are currently 280 crates with docs and 100+
                                        //        starts. This should be fine for a while.
                let name: String = rows.get(0).get(0);
                let version: String = rows.get(0).get(1);
                let target_name: String = rows.get(0).get(2);
                let url = Url::parse(&format!("{}://{}:{}/{}/{}/{}",
                                              req.url.scheme,
                                              req.url.host,
                                              req.url.port,
                                              name,
                                              version,
                                              target_name))
                    .unwrap();

                let mut resp = Response::with((status::Found, Redirect(url)));
                use iron::headers::{Expires, HttpDate};
                use time;
                resp.headers.set(Expires(HttpDate(time::now())));
                return Ok(resp);
            }

            if let Some(version) = match_version(&conn, &query, None) {
                let url = Url::parse(&format!("{}://{}:{}/crate/{}/{}",
                                              req.url.scheme,
                                              req.url.host,
                                              req.url.port,
                                              query,
                                              version)[..])
                    .unwrap();
                let mut resp = Response::with((status::Found, Redirect(url)));

                use iron::headers::{Expires, HttpDate};
                use time;
                resp.headers.set(Expires(HttpDate(time::now())));
                return Ok(resp);
            }
        }

        let search_query = query.replace(" ", " & ");
        get_search_results(&conn, &search_query, 1, RELEASES_IN_RELEASES)
            .ok_or(IronError::new(Nope::NoResults, status::NotFound))
            .and_then(|(_, results)| {
                // FIXME: There is no pagination
                Page::new(results)
                    .set("search_query", &query)
                    .title(&format!("Search results for '{}'", query))
                    .to_resp("releases")
            })
    } else {
        Err(IronError::new(Nope::NoResults, status::NotFound))
    }
}

pub fn activity_handler(req: &mut Request) -> IronResult<Response> {
    let conn = req.extensions.get::<Pool>().unwrap();
    let release_activity_data: Json =
        conn.query("SELECT value FROM config WHERE name = 'release_activity'",
                   &[])
            .unwrap()
            .get(0)
            .get(0);
    Page::new(release_activity_data)
        .title("Releases")
        .set("description", "Monthly release activity")
        .set_true("show_releases_navigation")
        .set_true("releases_navigation_activity_tab")
        .set_true("javascript_highchartjs")
        .to_resp("releases_activity")
}
<|endoftext|>


<commit_before>extern crate winapi;

use self::winapi::um::winuser::*;
use self::winapi::ctypes::c_int;

use {KeyboardControllable, Key, MouseControllable, MouseButton};
use win::keycodes::*;
use std::mem::*;

/// The main struct for handling the event emitting
#[derive(Default)]
pub struct Enigo;

fn mouse_event(flags: u32, data: u32, dx: i32, dy: i32) {
    let mut input = INPUT {
        type_: INPUT_MOUSE,
        u: unsafe {
            transmute(MOUSEINPUT {
                dx: dx,
                dy: dy,
                mouseData: data,
                dwFlags: flags,
                time: 0,
                dwExtraInfo: 0,
            })
        },
    };
    unsafe { SendInput(1, &mut input as LPINPUT, size_of::<INPUT>() as c_int) };
}

fn keybd_event(flags: u32, vk: u16, scan: u16) {
    let mut input = INPUT {
        type_: INPUT_KEYBOARD,
        u: unsafe {
            transmute_copy(&KEYBDINPUT {
                wVk: vk,
                wScan: scan,
                dwFlags: flags,
                time: 0,
                dwExtraInfo: 0,
            })
        },
    };
    unsafe { SendInput(1, &mut input as LPINPUT, size_of::<INPUT>() as c_int) };
}

impl MouseControllable for Enigo {
    fn mouse_move_to(&mut self, x: i32, y: i32) {
        mouse_event(
            MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE,
            0,
            x * 65335 / unsafe { GetSystemMetrics(78) },
            y * 65335 / unsafe { GetSystemMetrics(79) },
        );
    }

    fn mouse_move_relative(&mut self, x: i32, y: i32) {
        mouse_event(MOUSEEVENTF_MOVE, 0, x, y);
    }

    fn mouse_down(&mut self, button: MouseButton) {
        mouse_event(
            match button {
                MouseButton::Left => MOUSEEVENTF_LEFTDOWN,
                MouseButton::Middle => MOUSEEVENTF_MIDDLEDOWN,
                MouseButton::Right => MOUSEEVENTF_RIGHTDOWN,
                _ => unimplemented!(),
            },
            0,
            0,
            0,
        );
    }

    fn mouse_up(&mut self, button: MouseButton) {
        mouse_event(
            match button {
                MouseButton::Left => MOUSEEVENTF_LEFTUP,
                MouseButton::Middle => MOUSEEVENTF_MIDDLEUP,
                MouseButton::Right => MOUSEEVENTF_RIGHTUP,
                _ => unimplemented!(),
            },
            0,
            0,
            0,
        );
    }

    fn mouse_click(&mut self, button: MouseButton) {
        self.mouse_down(button);
        self.mouse_up(button);
    }

    fn mouse_scroll_x(&mut self, length: i32) {
        mouse_event(MOUSEEVENTF_HWHEEL, unsafe { transmute(length * 120) }, 0, 0);
    }

    fn mouse_scroll_y(&mut self, length: i32) {
        mouse_event(MOUSEEVENTF_WHEEL, unsafe { transmute(length * 120) }, 0, 0);
    }
}

impl KeyboardControllable for Enigo {
    fn key_sequence(&mut self, sequence: &str) {
        let mut buffer = [0; 2];

        for c in sequence.chars() {
            // Windows uses uft-16 encoding. We need to check
            // for variable length characters. As such some
            // characters can be 32 bit long and those are
            // encoded in such called hight and low surrogates
            // each 16 bit wide that needs to be send after
            // another to the SendInput function without
            // being interrupted by "keyup"
            let result = c.encode_utf16(&mut buffer);
            if result.len() == 1 {
                self.unicode_key_click(result[0]);
            } else {
                for utf16_surrogate in result {
                    self.unicode_key_down(utf16_surrogate.clone());
                }
                // do i need to produce a keyup?
                // self.unicode_key_up(0);
            }
        }
    }

    fn key_click(&mut self, key: Key) {
        let scancode = self.key_to_scancode(key);
        use std::{thread, time};
        keybd_event(KEYEVENTF_SCANCODE, 0, scancode);
        thread::sleep(time::Duration::from_millis(20));
        keybd_event(KEYEVENTF_KEYUP | KEYEVENTF_SCANCODE, 0, scancode);
    }

    fn key_down(&mut self, key: Key) {
        keybd_event(KEYEVENTF_SCANCODE, 0, self.key_to_scancode(key));
    }

    fn key_up(&mut self, key: Key) {
        keybd_event(KEYEVENTF_KEYUP | KEYEVENTF_SCANCODE, 0, self.key_to_scancode(key));
    }
}

impl Enigo {
    fn unicode_key_click(&self, unicode_char: u16) {
        use std::{thread, time};
        self.unicode_key_down(unicode_char);
        thread::sleep(time::Duration::from_millis(20));
        self.unicode_key_up(unicode_char);
    }

    fn unicode_key_down(&self, unicode_char: u16) {
        keybd_event(KEYEVENTF_UNICODE, 0, unicode_char);
    }

    fn unicode_key_up(&self, unicode_char: u16) {
        keybd_event(KEYEVENTF_UNICODE | KEYEVENTF_KEYUP, 0, unicode_char);
    }

    fn key_to_keycode(&self, key: Key) -> u16 {
        // do not use the codes from crate winapi they're
        // wrongly typed with i32 instead of i16 use the
        // ones provided by win/keycodes.rs that are prefixed
        // with an 'E' infront of the original name
        #[allow(deprecated)] // I mean duh, we still need to support deprecated keys until they're removed
        match key {
            Key::Alt => EVK_MENU,
            Key::Backspace => EVK_BACK,
            Key::CapsLock => EVK_CAPITAL,
            Key::Control => EVK_LCONTROL,
            Key::Delete => EVK_DELETE,
            Key::DownArrow => EVK_DOWN,
            Key::End => EVK_END,
            Key::Escape => EVK_ESCAPE,
            Key::F1 => EVK_F1,
            Key::F10 => EVK_F10,
            Key::F11 => EVK_F11,
            Key::F12 => EVK_F12,
            Key::F2 => EVK_F2,
            Key::F3 => EVK_F3,
            Key::F4 => EVK_F4,
            Key::F5 => EVK_F5,
            Key::F6 => EVK_F6,
            Key::F7 => EVK_F7,
            Key::F8 => EVK_F8,
            Key::F9 => EVK_F9,
            Key::Home => EVK_HOME,
            Key::LeftArrow => EVK_LEFT,
            Key::Option => EVK_MENU,
            Key::PageDown => EVK_NEXT,
            Key::PageUp => EVK_PRIOR,
            Key::Return => EVK_RETURN,
            Key::RightArrow => EVK_RIGHT,
            Key::Shift => EVK_SHIFT,
            Key::Space => EVK_SPACE,
            Key::Tab => EVK_TAB,
            Key::UpArrow => EVK_UP,

            Key::Raw(raw_keycode) => raw_keycode,
            Key::Layout(c) => self.get_layoutdependent_keycode(c.to_string()),
            //_ => 0,

            Key::Super |
            Key::Command |
            Key::Windows |
            Key::Meta => EVK_LWIN
        }
    }

    fn key_to_scancode(&self, key: Key) -> u16
    {
        let keycode = self.key_to_keycode(key);
        unsafe {
            MapVirtualKeyW(keycode as u32, 0) as u16
        }
    }

    fn get_layoutdependent_keycode(&self, string: String) -> u16 {
        let mut buffer = [0; 2];
        // get the first char from the string ignore the rest
        // ensure its not a multybyte char
        let utf16 = string
            .chars()
            .nth(0)
            .expect("no valid input") //TODO(dustin): no panic here make an error
            .encode_utf16(&mut buffer);
        if utf16.len() != 1 {
            // TODO(dustin) don't panic here use an apropriate errors
            panic!("this char is not allowd");
        }
        // NOTE VkKeyScanW uses the current keyboard layout
        // to specify a layout use VkKeyScanExW and GetKeyboardLayout
        // or load one with LoadKeyboardLayoutW
        let keycode_and_shiftstate = unsafe { VkKeyScanW(utf16[0]) };
        // 0x41 as u16 //key that has the letter 'a' on it on english like keylayout
        keycode_and_shiftstate as u16
    }
}
<commit_msg>More APIs to top-level Enigo impl for windows.<commit_after>extern crate winapi;

use self::winapi::shared::windef::POINT;
use self::winapi::um::winuser::*;
use self::winapi::ctypes::c_int;

use {KeyboardControllable, Key, MouseControllable, MouseButton};
use win::keycodes::*;
use std::mem::*;

/// The main struct for handling the event emitting
#[derive(Default)]
pub struct Enigo;

fn mouse_event(flags: u32, data: u32, dx: i32, dy: i32) {
    let mut input = INPUT {
        type_: INPUT_MOUSE,
        u: unsafe {
            transmute(MOUSEINPUT {
                dx: dx,
                dy: dy,
                mouseData: data,
                dwFlags: flags,
                time: 0,
                dwExtraInfo: 0,
            })
        },
    };
    unsafe { SendInput(1, &mut input as LPINPUT, size_of::<INPUT>() as c_int) };
}

fn keybd_event(flags: u32, vk: u16, scan: u16) {
    let mut input = INPUT {
        type_: INPUT_KEYBOARD,
        u: unsafe {
            transmute_copy(&KEYBDINPUT {
                wVk: vk,
                wScan: scan,
                dwFlags: flags,
                time: 0,
                dwExtraInfo: 0,
            })
        },
    };
    unsafe { SendInput(1, &mut input as LPINPUT, size_of::<INPUT>() as c_int) };
}

impl MouseControllable for Enigo {
    fn mouse_move_to(&mut self, x: i32, y: i32) {
        mouse_event(
            MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE,
            0,
            x * 65335 / unsafe { GetSystemMetrics(78) },
            y * 65335 / unsafe { GetSystemMetrics(79) },
        );
    }

    fn mouse_move_relative(&mut self, x: i32, y: i32) {
        mouse_event(MOUSEEVENTF_MOVE, 0, x, y);
    }

    fn mouse_down(&mut self, button: MouseButton) {
        mouse_event(
            match button {
                MouseButton::Left => MOUSEEVENTF_LEFTDOWN,
                MouseButton::Middle => MOUSEEVENTF_MIDDLEDOWN,
                MouseButton::Right => MOUSEEVENTF_RIGHTDOWN,
                _ => unimplemented!(),
            },
            0,
            0,
            0,
        );
    }

    fn mouse_up(&mut self, button: MouseButton) {
        mouse_event(
            match button {
                MouseButton::Left => MOUSEEVENTF_LEFTUP,
                MouseButton::Middle => MOUSEEVENTF_MIDDLEUP,
                MouseButton::Right => MOUSEEVENTF_RIGHTUP,
                _ => unimplemented!(),
            },
            0,
            0,
            0,
        );
    }

    fn mouse_click(&mut self, button: MouseButton) {
        self.mouse_down(button);
        self.mouse_up(button);
    }

    fn mouse_scroll_x(&mut self, length: i32) {
        mouse_event(MOUSEEVENTF_HWHEEL, unsafe { transmute(length * 120) }, 0, 0);
    }

    fn mouse_scroll_y(&mut self, length: i32) {
        mouse_event(MOUSEEVENTF_WHEEL, unsafe { transmute(length * 120) }, 0, 0);
    }
}

impl KeyboardControllable for Enigo {
    fn key_sequence(&mut self, sequence: &str) {
        let mut buffer = [0; 2];

        for c in sequence.chars() {
            // Windows uses uft-16 encoding. We need to check
            // for variable length characters. As such some
            // characters can be 32 bit long and those are
            // encoded in such called hight and low surrogates
            // each 16 bit wide that needs to be send after
            // another to the SendInput function without
            // being interrupted by "keyup"
            let result = c.encode_utf16(&mut buffer);
            if result.len() == 1 {
                self.unicode_key_click(result[0]);
            } else {
                for utf16_surrogate in result {
                    self.unicode_key_down(utf16_surrogate.clone());
                }
                // do i need to produce a keyup?
                // self.unicode_key_up(0);
            }
        }
    }

    fn key_click(&mut self, key: Key) {
        let scancode = self.key_to_scancode(key);
        use std::{thread, time};
        keybd_event(KEYEVENTF_SCANCODE, 0, scancode);
        thread::sleep(time::Duration::from_millis(20));
        keybd_event(KEYEVENTF_KEYUP | KEYEVENTF_SCANCODE, 0, scancode);
    }

    fn key_down(&mut self, key: Key) {
        keybd_event(KEYEVENTF_SCANCODE, 0, self.key_to_scancode(key));
    }

    fn key_up(&mut self, key: Key) {
        keybd_event(KEYEVENTF_KEYUP | KEYEVENTF_SCANCODE, 0, self.key_to_scancode(key));
    }
}

impl Enigo {
    /// Gets the (width, height) of the main display in screen coordinates (pixels).
    ///
    /// # Example
    ///
    /// ```no_run
    /// use enigo::*;
    /// let mut size = Enigo::main_display_size();
    /// ```
    pub fn main_display_size() -> (usize, usize) {
      let w = unsafe { GetSystemMetrics(SM_CXSCREEN) as usize };
      let h = unsafe { GetSystemMetrics(SM_CYSCREEN) as usize };
      (w, h)
    }

    /// Gets the location of mouse in screen coordinates (pixels).
    ///
    /// # Example
    ///
    /// ```no_run
    /// use enigo::*;
    /// let mut location = Enigo::mouse_location();
    /// ```
    pub fn mouse_location() -> (i32, i32) {
        let mut point = POINT { x: 0, y: 0 };
        let result = unsafe { GetCursorPos(&mut point) };
        if result != 0 {
          (point.x, point.y)
        } else {
          (0, 0)
        }
    }

    fn unicode_key_click(&self, unicode_char: u16) {
        use std::{thread, time};
        self.unicode_key_down(unicode_char);
        thread::sleep(time::Duration::from_millis(20));
        self.unicode_key_up(unicode_char);
    }

    fn unicode_key_down(&self, unicode_char: u16) {
        keybd_event(KEYEVENTF_UNICODE, 0, unicode_char);
    }

    fn unicode_key_up(&self, unicode_char: u16) {
        keybd_event(KEYEVENTF_UNICODE | KEYEVENTF_KEYUP, 0, unicode_char);
    }

    fn key_to_keycode(&self, key: Key) -> u16 {
        // do not use the codes from crate winapi they're
        // wrongly typed with i32 instead of i16 use the
        // ones provided by win/keycodes.rs that are prefixed
        // with an 'E' infront of the original name
        #[allow(deprecated)] // I mean duh, we still need to support deprecated keys until they're removed
        match key {
            Key::Alt => EVK_MENU,
            Key::Backspace => EVK_BACK,
            Key::CapsLock => EVK_CAPITAL,
            Key::Control => EVK_LCONTROL,
            Key::Delete => EVK_DELETE,
            Key::DownArrow => EVK_DOWN,
            Key::End => EVK_END,
            Key::Escape => EVK_ESCAPE,
            Key::F1 => EVK_F1,
            Key::F10 => EVK_F10,
            Key::F11 => EVK_F11,
            Key::F12 => EVK_F12,
            Key::F2 => EVK_F2,
            Key::F3 => EVK_F3,
            Key::F4 => EVK_F4,
            Key::F5 => EVK_F5,
            Key::F6 => EVK_F6,
            Key::F7 => EVK_F7,
            Key::F8 => EVK_F8,
            Key::F9 => EVK_F9,
            Key::Home => EVK_HOME,
            Key::LeftArrow => EVK_LEFT,
            Key::Option => EVK_MENU,
            Key::PageDown => EVK_NEXT,
            Key::PageUp => EVK_PRIOR,
            Key::Return => EVK_RETURN,
            Key::RightArrow => EVK_RIGHT,
            Key::Shift => EVK_SHIFT,
            Key::Space => EVK_SPACE,
            Key::Tab => EVK_TAB,
            Key::UpArrow => EVK_UP,

            Key::Raw(raw_keycode) => raw_keycode,
            Key::Layout(c) => self.get_layoutdependent_keycode(c.to_string()),
            //_ => 0,

            Key::Super |
            Key::Command |
            Key::Windows |
            Key::Meta => EVK_LWIN
        }
    }

    fn key_to_scancode(&self, key: Key) -> u16
    {
        let keycode = self.key_to_keycode(key);
        unsafe {
            MapVirtualKeyW(keycode as u32, 0) as u16
        }
    }

    fn get_layoutdependent_keycode(&self, string: String) -> u16 {
        let mut buffer = [0; 2];
        // get the first char from the string ignore the rest
        // ensure its not a multybyte char
        let utf16 = string
            .chars()
            .nth(0)
            .expect("no valid input") //TODO(dustin): no panic here make an error
            .encode_utf16(&mut buffer);
        if utf16.len() != 1 {
            // TODO(dustin) don't panic here use an apropriate errors
            panic!("this char is not allowd");
        }
        // NOTE VkKeyScanW uses the current keyboard layout
        // to specify a layout use VkKeyScanExW and GetKeyboardLayout
        // or load one with LoadKeyboardLayoutW
        let keycode_and_shiftstate = unsafe { VkKeyScanW(utf16[0]) };
        // 0x41 as u16 //key that has the letter 'a' on it on english like keylayout
        keycode_and_shiftstate as u16
    }
}
<|endoftext|>


<commit_before>// extern crate csv;
// extern crate diesel;
// #[macro_use]
// extern crate serde_derive;

// #[derive(Debug, Serialize)]
// struct Record {
//     pub id: String,
//     pub html: String,
//     pub political: i32,
//     pub not_political: i32,
//     pub title: String,
//     pub message: String,
//     pub thumbnail: String,
//     pub created_at: DateTime<Utc>,
//     pub updated_at: DateTime<Utc>,
//     pub lang: String,
//     pub images: Vec<String>,
//     pub impressions: i32,
//     pub political_probability: f64,
//     pub targeting: Option<String>,
//     #[serde(skip_serializing)]
//     pub suppressed: bool,
//     pub targets: Option<String>,
//     pub advertiser: Option<String>,
//     pub entities: Option<String>,
//     pub page: Option<String>,
// }
fn main() {}
<commit_msg>good start for archiver<commit_after>extern crate csv;
extern crate diesel;
#[macro_use]
extern crate serde_derive;

#[derive(Debug, Serialize)]
struct Record {
    pub id: String,
    pub html: String,
    pub political: i32,
    pub not_political: i32,
    pub title: String,
    pub message: String,
    pub thumbnail: String,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub lang: String,
    pub images: Vec<String>,
    pub impressions: i32,
    pub political_probability: f64,
    pub targeting: Option<String>,
    pub targets: Option<String>,
    pub advertiser: Option<String>,
    pub entities: Option<String>,
    pub page: Option<String>,
}

impl From<Ad> for Record {
    fn from(ad: Ad) -> Self {
        Record {
            targets: serde_json::to_string(&ad.targets).ok(),
            entities: serde_json::to_string(&ad.entities).ok(),
            images: serde_json::to_string(&ad.images).ok(),
            id: ad.id,
            html: ad.html,
            political: ad.political,
            not_political: ad.not_political,
            title: ad.title,
            message: ad.message,
            thumbnail: ad.thumbnail,
            created_at: ad.created_at,
            updated_at: ad.updated_at,
            lang: ad.lang,
            impressions: ad.impressions,
            political_probability: ad.political_probability,
            targeting: ad.targeting,
            page: ad.page
        }
    }
}

fn main() {
    dotenv().ok();
    start_logging();
    let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
    let conn = PgConnection::establish(&database_url).unwrap();

}
<|endoftext|>


<commit_before>extern crate open_location_code;
extern crate geo;

use std::vec::Vec;

use open_location_code::{is_valid, is_short, is_full};
use open_location_code::{encode, decode};
use open_location_code::{shorten, recover_nearest};

use geo::Point;

mod csv_reader;

use csv_reader::CSVReader;

/// CSVReader is written to swallow errors; as such, we might "pass" tests because we didn't
/// actually run any!  Thus, we use 'tested' below to count # lines read and hence to assert that
/// > 0 tests were executed.
///
/// We could probably take it a little further, and assert that tested was >= # tests in the file
/// (allowing tests to be added, but assuming # tests will never be reduced).

#[test]
fn is_valid_test() {
    let mut tested = 0;
    for line in CSVReader::new("validityTests.csv") {
        let cols: Vec<&str> = line.split(',').collect();
        let code = cols[0];
        let _valid = cols[1] == "true";
        let _short = cols[2] == "true";
        let _full = cols[3] == "true";

        assert_eq!(is_valid(code), _valid, "valid for code: {}", code);
        assert_eq!(is_short(code), _short, "short for code: {}", code);
        assert_eq!(is_full(code), _full, "full for code: {}", code);

        tested += 1;
    }
    assert!(tested > 0);
}

#[test]
fn encode_decode_test() {
    let mut tested = 0;
    for line in CSVReader::new("encodingTests.csv") {
        let cols: Vec<&str> = line.split(',').collect();
        let code = cols[0];
        let lat = cols[1].parse::<f64>().unwrap();
        let lng = cols[2].parse::<f64>().unwrap();
        let latlo = cols[3].parse::<f64>().unwrap();
        let lnglo = cols[4].parse::<f64>().unwrap();
        let lathi = cols[5].parse::<f64>().unwrap();
        let lnghi = cols[6].parse::<f64>().unwrap();

        let codearea = decode(code).unwrap();
        assert_eq!(
            encode(Point::new(lng, lat), codearea.code_length),
            code,
            "encoding lat={},lng={}", lat, lng
        );
        assert!((latlo - codearea.south).abs() < 1e-10f64);
        assert!((lathi - codearea.north).abs() < 1e-10f64);
        assert!((lnglo - codearea.west).abs() < 1e-10f64);
        assert!((lnghi - codearea.east).abs() < 1e-10f64);

        tested += 1;
    }
    assert!(tested > 0);
}

#[test]
fn shorten_recovery_test() {
    let mut tested = 0;
    for line in CSVReader::new("shortCodeTests.csv") {
        let cols: Vec<&str> = line.split(',').collect();
        let full_code = cols[0];
        let lat = cols[1].parse::<f64>().unwrap();
        let lng = cols[2].parse::<f64>().unwrap();
        let short_code = cols[3];
        let test_type = cols[4];

        if test_type == 'B' || test_type == 'S' {
            assert_eq!(shorten(full_code, Point::new(lng, lat)).unwrap(), short_code, "shorten");
        }
        if test_type == 'B' || test_type == 'R' {
            assert_eq!(
                recover_nearest(short_code, Point::new(lng, lat)),
                Ok(full_code.to_string()),
                "recover"
            );
        }

        tested += 1;
    }

    assert!(tested > 0);
}

<commit_msg>Update all_test.rs<commit_after>extern crate open_location_code;
extern crate geo;

use std::vec::Vec;

use open_location_code::{is_valid, is_short, is_full};
use open_location_code::{encode, decode};
use open_location_code::{shorten, recover_nearest};

use geo::Point;

mod csv_reader;

use csv_reader::CSVReader;

/// CSVReader is written to swallow errors; as such, we might "pass" tests because we didn't
/// actually run any!  Thus, we use 'tested' below to count # lines read and hence to assert that
/// > 0 tests were executed.
///
/// We could probably take it a little further, and assert that tested was >= # tests in the file
/// (allowing tests to be added, but assuming # tests will never be reduced).

#[test]
fn is_valid_test() {
    let mut tested = 0;
    for line in CSVReader::new("validityTests.csv") {
        let cols: Vec<&str> = line.split(',').collect();
        let code = cols[0];
        let _valid = cols[1] == "true";
        let _short = cols[2] == "true";
        let _full = cols[3] == "true";

        assert_eq!(is_valid(code), _valid, "valid for code: {}", code);
        assert_eq!(is_short(code), _short, "short for code: {}", code);
        assert_eq!(is_full(code), _full, "full for code: {}", code);

        tested += 1;
    }
    assert!(tested > 0);
}

#[test]
fn encode_decode_test() {
    let mut tested = 0;
    for line in CSVReader::new("encodingTests.csv") {
        let cols: Vec<&str> = line.split(',').collect();
        let code = cols[0];
        let lat = cols[1].parse::<f64>().unwrap();
        let lng = cols[2].parse::<f64>().unwrap();
        let latlo = cols[3].parse::<f64>().unwrap();
        let lnglo = cols[4].parse::<f64>().unwrap();
        let lathi = cols[5].parse::<f64>().unwrap();
        let lnghi = cols[6].parse::<f64>().unwrap();

        let codearea = decode(code).unwrap();
        assert_eq!(
            encode(Point::new(lng, lat), codearea.code_length),
            code,
            "encoding lat={},lng={}", lat, lng
        );
        assert!((latlo - codearea.south).abs() < 1e-10f64);
        assert!((lathi - codearea.north).abs() < 1e-10f64);
        assert!((lnglo - codearea.west).abs() < 1e-10f64);
        assert!((lnghi - codearea.east).abs() < 1e-10f64);

        tested += 1;
    }
    assert!(tested > 0);
}

#[test]
fn shorten_recovery_test() {
    let mut tested = 0;
    for line in CSVReader::new("shortCodeTests.csv") {
        let cols: Vec<&str> = line.split(',').collect();
        let full_code = cols[0];
        let lat = cols[1].parse::<f64>().unwrap();
        let lng = cols[2].parse::<f64>().unwrap();
        let short_code = cols[3];
        let test_type = cols[4];

        if test_type == "B" || test_type == "S" {
            assert_eq!(shorten(full_code, Point::new(lng, lat)).unwrap(), short_code, "shorten");
        }
        if test_type == "B" || test_type == "R" {
            assert_eq!(
                recover_nearest(short_code, Point::new(lng, lat)),
                Ok(full_code.to_string()),
                "recover"
            );
        }

        tested += 1;
    }

    assert!(tested > 0);
}

<|endoftext|>


<commit_before>use std::cmp::Ordering;
use value::TwigValueRef;
use runtime;

#[derive(PartialEq, Debug, Copy, Clone)]
pub enum OperatorKind {
    /// Single argument operator, i.e negation.
    Unary,
    /// Two argument operator, i.e sum.
    Binary(Associativity)
}

impl OperatorKind {
    pub fn new_binary(associativity: Associativity) -> OperatorKind {
        OperatorKind::Binary(associativity)
    }

    pub fn new_binary_left() -> OperatorKind {
        OperatorKind::Binary(Associativity::Left)
    }

    pub fn new_binary_right() -> OperatorKind {
        OperatorKind::Binary(Associativity::Right)
    }

    pub fn new_unary() -> OperatorKind {
        OperatorKind::Unary
    }
}

#[derive(Debug, Copy, Clone)]
pub struct OperatorOptions {
    pub value: &'static str,
    pub precedence: u16,
    pub kind: OperatorKind,
}

impl OperatorOptions {

    pub fn new_binary(chars: &'static str, precedence: u16, associativity: Associativity) -> OperatorOptions {
        OperatorOptions {
            value: chars,
            precedence: precedence,
            kind: OperatorKind::new_binary(associativity),
        }
    }

    pub fn new_binary_left(chars: &'static str, precedence: u16) -> OperatorOptions {
        OperatorOptions::new_binary(chars, precedence, Associativity::Left)
    }

    pub fn new_binary_right(chars: &'static str, precedence: u16) -> OperatorOptions {
        OperatorOptions::new_binary(chars, precedence, Associativity::Right)
    }

    pub fn new_unary(chars: &'static str, precedence: u16) -> OperatorOptions {
        OperatorOptions {
            value: chars,
            precedence: precedence,
            kind: OperatorKind::new_unary(),
        }
    }
}

pub struct Operator {
    pub options: OperatorOptions,
    pub callable: Box<
        for<'e, 'z> Fn(&'e [TwigValueRef<'z>]) -> runtime::Result<TwigValueRef<'z>>
    >,
}

impl Operator {

    pub fn new_binary<F: 'static>(
        chars: &'static str,
        precedence: u16,
        associativity: Associativity,
        callable: F
    )
        -> Operator
    where
        F: for<'e, 'z> Fn(&'e TwigValueRef<'z>, &'e TwigValueRef<'z>) -> runtime::Result<TwigValueRef<'z>>
    {
        Operator {
            options: OperatorOptions::new_binary(chars, precedence, associativity),
            callable: Box::new(move |args| {
                if args.len() != 2 {
                    return Err(runtime::Error::new(
                        runtime::ErrorMessage::InvalidArgumentCount {
                            expected: 2,
                            found: args.len()
                        }
                    ))
                }

                callable(
                    unsafe { args.get_unchecked(0) },
                    unsafe { args.get_unchecked(1) }
                )
            }),
        }
    }

    pub fn new_binary_left<F: 'static>(
        chars: &'static str,
        precedence: u16,
        callable: F
    )
        -> Operator
    where
        F: for<'e, 'z> Fn(&'e TwigValueRef<'z>, &'e TwigValueRef<'z>) -> runtime::Result<TwigValueRef<'z>>
    {
        Operator::new_binary(
            chars,
            precedence,
            Associativity::Left,
            callable
        )
    }

    pub fn new_binary_right<F: 'static>(
        chars: &'static str,
        precedence: u16,
        callable: F
    )
        -> Operator
    where
        F: for<'e, 'z> Fn(&'e TwigValueRef<'z>, &'e TwigValueRef<'z>) -> runtime::Result<TwigValueRef<'z>>
    {
        Operator::new_binary(
            chars,
            precedence,
            Associativity::Right,
            callable
        )
    }

    pub fn new_unary<F: 'static>(
        chars: &'static str,
        precedence: u16,
        callable: F
    )
        -> Operator
    where
        F: for<'e, 'z> Fn(&'e TwigValueRef<'z>) -> runtime::Result<TwigValueRef<'z>>
    {
        Operator {
            options: OperatorOptions::new_unary(chars, precedence),
            callable: Box::new(move |args| {
                if args.len() != 1 {
                    return Err(runtime::Error::new(
                        runtime::ErrorMessage::InvalidArgumentCount {
                            expected: 1,
                            found: args.len()
                        }
                    ))
                }

                callable(
                    unsafe { args.get_unchecked(0) }
                )
            }),
        }
    }
}

/// Operator associativity.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Associativity {
    Left,
    Right,
}

impl PartialOrd for Associativity {
    fn partial_cmp(&self, other: &Associativity) -> Option<Ordering> {
        match (*self, *other) {
            (Associativity::Left, Associativity::Right) => Some(Ordering::Less),
            (Associativity::Right, Associativity::Left) => Some(Ordering::Greater),
            _ => Some(Ordering::Equal),
        }
    }
}

impl Ord for Associativity {
    fn cmp(&self, other: &Associativity) -> Ordering {
        match (*self, *other) {
            (Associativity::Left, Associativity::Right) => Ordering::Less,
            (Associativity::Right, Associativity::Left) => Ordering::Greater,
            _ => Ordering::Equal,
        }
    }
}

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

    #[test]
    fn associativity_left_should_be_less_than_right() {
        assert!(Associativity::Left < Associativity::Right);
    }

    #[test]
    fn associativity_right_should_be_greater_than_left() {
        assert!(Associativity::Right > Associativity::Left);
    }

    #[test]
    fn associativity_right_should_be_equal_to_right() {
        assert!(Associativity::Right == Associativity::Right);
    }
}
<commit_msg>Make Operator use owned version of TwigValue.<commit_after>use std::cmp::Ordering;
use value::TwigValue;
use runtime;

#[derive(PartialEq, Debug, Copy, Clone)]
pub enum OperatorKind {
    /// Single argument operator, i.e negation.
    Unary,
    /// Two argument operator, i.e sum.
    Binary(Associativity)
}

impl OperatorKind {
    pub fn new_binary(associativity: Associativity) -> OperatorKind {
        OperatorKind::Binary(associativity)
    }

    pub fn new_binary_left() -> OperatorKind {
        OperatorKind::Binary(Associativity::Left)
    }

    pub fn new_binary_right() -> OperatorKind {
        OperatorKind::Binary(Associativity::Right)
    }

    pub fn new_unary() -> OperatorKind {
        OperatorKind::Unary
    }
}

#[derive(Debug, Copy, Clone)]
pub struct OperatorOptions {
    pub value: &'static str,
    pub precedence: u16,
    pub kind: OperatorKind,
}

impl OperatorOptions {

    pub fn new_binary(chars: &'static str, precedence: u16, associativity: Associativity) -> OperatorOptions {
        OperatorOptions {
            value: chars,
            precedence: precedence,
            kind: OperatorKind::new_binary(associativity),
        }
    }

    pub fn new_binary_left(chars: &'static str, precedence: u16) -> OperatorOptions {
        OperatorOptions::new_binary(chars, precedence, Associativity::Left)
    }

    pub fn new_binary_right(chars: &'static str, precedence: u16) -> OperatorOptions {
        OperatorOptions::new_binary(chars, precedence, Associativity::Right)
    }

    pub fn new_unary(chars: &'static str, precedence: u16) -> OperatorOptions {
        OperatorOptions {
            value: chars,
            precedence: precedence,
            kind: OperatorKind::new_unary(),
        }
    }
}

pub struct Operator {
    pub options: OperatorOptions,
    pub callable: Box<
        for<'e, 'z> Fn(&'e [TwigValue]) -> runtime::Result<TwigValue>
    >,
}

impl Operator {

    pub fn new_binary<F: 'static>(
        chars: &'static str,
        precedence: u16,
        associativity: Associativity,
        callable: F
    )
        -> Operator
    where
        F: for<'e, 'z> Fn(&'e TwigValue, &'e TwigValue) -> runtime::Result<TwigValue>
    {
        Operator {
            options: OperatorOptions::new_binary(chars, precedence, associativity),
            callable: Box::new(move |args| {
                if args.len() != 2 {
                    return Err(runtime::Error::new(
                        runtime::ErrorMessage::InvalidArgumentCount {
                            expected: 2,
                            found: args.len()
                        }
                    ))
                }

                callable(
                    unsafe { args.get_unchecked(0) },
                    unsafe { args.get_unchecked(1) }
                )
            }),
        }
    }

    pub fn new_binary_left<F: 'static>(
        chars: &'static str,
        precedence: u16,
        callable: F
    )
        -> Operator
    where
        F: for<'e, 'z> Fn(&'e TwigValue, &'e TwigValue) -> runtime::Result<TwigValue>
    {
        Operator::new_binary(
            chars,
            precedence,
            Associativity::Left,
            callable
        )
    }

    pub fn new_binary_right<F: 'static>(
        chars: &'static str,
        precedence: u16,
        callable: F
    )
        -> Operator
    where
        F: for<'e, 'z> Fn(&'e TwigValue, &'e TwigValue) -> runtime::Result<TwigValue>
    {
        Operator::new_binary(
            chars,
            precedence,
            Associativity::Right,
            callable
        )
    }

    pub fn new_unary<F: 'static>(
        chars: &'static str,
        precedence: u16,
        callable: F
    )
        -> Operator
    where
        F: for<'e, 'z> Fn(&'e TwigValue) -> runtime::Result<TwigValue>
    {
        Operator {
            options: OperatorOptions::new_unary(chars, precedence),
            callable: Box::new(move |args| {
                if args.len() != 1 {
                    return Err(runtime::Error::new(
                        runtime::ErrorMessage::InvalidArgumentCount {
                            expected: 1,
                            found: args.len()
                        }
                    ))
                }

                callable(
                    unsafe { args.get_unchecked(0) }
                )
            }),
        }
    }
}

/// Operator associativity.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Associativity {
    Left,
    Right,
}

impl PartialOrd for Associativity {
    fn partial_cmp(&self, other: &Associativity) -> Option<Ordering> {
        match (*self, *other) {
            (Associativity::Left, Associativity::Right) => Some(Ordering::Less),
            (Associativity::Right, Associativity::Left) => Some(Ordering::Greater),
            _ => Some(Ordering::Equal),
        }
    }
}

impl Ord for Associativity {
    fn cmp(&self, other: &Associativity) -> Ordering {
        match (*self, *other) {
            (Associativity::Left, Associativity::Right) => Ordering::Less,
            (Associativity::Right, Associativity::Left) => Ordering::Greater,
            _ => Ordering::Equal,
        }
    }
}

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

    #[test]
    fn associativity_left_should_be_less_than_right() {
        assert!(Associativity::Left < Associativity::Right);
    }

    #[test]
    fn associativity_right_should_be_greater_than_left() {
        assert!(Associativity::Right > Associativity::Left);
    }

    #[test]
    fn associativity_right_should_be_equal_to_right() {
        assert!(Associativity::Right == Associativity::Right);
    }
}
<|endoftext|>