<commit_before>// Copyright © 2018, 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 cosmogony::{Zone, ZoneIndex};
use failure::Error;
use mimir::objects::Admin;
use mimir::rubber::{IndexSettings, Rubber};
use mimirsbrunn::osm_reader::admin;
use mimirsbrunn::osm_reader::osm_utils;
use mimirsbrunn::utils;
use slog_scope::{info, warn};
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::sync::Arc;
use structopt::StructOpt;

trait IntoAdmin {
    fn into_admin(
        self,
        _: &BTreeMap<ZoneIndex, (String, Option<String>)>,
        langs: &[String],
        retrocompat_on_french_id: bool,
        max_weight: f64,
        all_admins: Option<&HashMap<String, Arc<Admin>>>,
    ) -> Admin;
}

fn get_weight(tags: &osmpbfreader::Tags, center_tags: &osmpbfreader::Tags) -> f64 {
    // to have an admin weight we use the osm 'population' tag to priorize
    // the big zones over the small one.
    // Note: this tags is not often filled , so only some zones
    // will have a weight (but the main cities have it).
    tags.get("population")
        .and_then(|p| p.parse().ok())
        .or_else(|| center_tags.get("population")?.parse().ok())
        .unwrap_or(0.)
}

impl IntoAdmin for Zone {
    fn into_admin(
        self,
        zones_osm_id: &BTreeMap<ZoneIndex, (String, Option<String>)>,
        langs: &[String],
        french_id_retrocompatibility: bool,
        max_weight: f64,
        all_admins: Option<&HashMap<String, Arc<Admin>>>,
    ) -> Admin {
        let insee = admin::read_insee(&self.tags).map(|s| s.to_owned());
        let zip_codes = admin::read_zip_codes(&self.tags);
        let label = self.label;
        let weight = get_weight(&self.tags, &self.center_tags);
        let center = self.center.map_or(mimir::Coord::default(), |c| {
            mimir::Coord::new(c.lng(), c.lat())
        });
        let format_id = |id, insee| {
            // for retrocompatibity reasons, Navitia needs the
            // french admins to have an id with the insee for cities
            match insee {
                Some(insee) if french_id_retrocompatibility => format!("admin:fr:{}", insee),
                _ => format!("admin:osm:{}", id),
            }
        };
        let parent_osm_id = self
            .parent
            .and_then(|id| zones_osm_id.get(&id))
            .map(|(id, insee)| format_id(id, insee.as_ref()));
        let codes = osm_utils::get_osm_codes_from_tags(&self.tags);
        let mut admin = Admin {
            id: zones_osm_id
                .get(&self.id)
                .map(|(id, insee)| format_id(id, insee.as_ref()))
                .expect("unable to find zone id in zones_osm_id"),
            insee: insee.unwrap_or("".to_owned()),
            level: self.admin_level.unwrap_or(0),
            label: label,
            name: self.name,
            zip_codes: zip_codes,
            weight: utils::normalize_weight(weight, max_weight),
            bbox: self.bbox,
            boundary: self.boundary,
            coord: center.clone(),
            approx_coord: Some(center.into()),
            zone_type: self.zone_type,
            parent_id: parent_osm_id,
            // Note: Since we do not really attach an admin to its hierarchy, for the moment an admin only have it's own coutry code,
            // not the country code of it's country from the hierarchy
            // (so it has a country code mainly if it is a country)
            country_codes: utils::get_country_code(&codes).into_iter().collect(),
            codes: codes,
            names: osm_utils::get_names_from_tags(&self.tags, &langs),
            labels: self
                .international_labels
                .into_iter()
                .filter(|(k, _)| langs.contains(&k))
                .collect(),
            distance: None,
            context: None,
            administrative_regions: Vec::new(),
        };
        if let Some(ref admins) = all_admins {
            // Get a list of encompassing parent ids, which will be used as the get
            // administrative_regions.
            let mut parent_ids = Vec::new();
            let mut current = &admin;
            while current.parent_id.is_some() {
                parent_ids.push(current.parent_id.clone().unwrap());
                if let Some(par) = admins.get(parent_ids.last().unwrap()) {
                    current = par;
                } else {
                    break;
                }
            }
            admin.administrative_regions = parent_ids
                .into_iter()
                .filter_map(|a| admins.get(&a))
                .map(|x| Arc::clone(x))
                .collect::<Vec<_>>();
        }
        admin
    }
}

fn send_to_es(
    admins: impl Iterator<Item = Admin>,
    cnx_string: &str,
    dataset: &str,
    index_settings: IndexSettings,
) -> Result<(), Error> {
    let mut rubber = Rubber::new(cnx_string);
    rubber.initialize_templates()?;
    let nb_admins = rubber.public_index(dataset, &index_settings, admins)?;
    info!("{} admins added.", nb_admins);
    Ok(())
}

fn read_zones(input: &str) -> Result<impl Iterator<Item = Zone>, Error> {
    Ok(cosmogony::read_zones_from_file(input)?
        .filter_map(|r| r.map_err(|e| warn!("impossible to read zone: {}", e)).ok()))
}

fn index_cosmogony(args: Args) -> Result<(), Error> {
    info!("building maps");
    use cosmogony::ZoneType::City;

    let mut cosmogony_id_to_osm_id = BTreeMap::new();
    let max_weight = utils::ADMIN_MAX_WEIGHT;
    let zones = read_zones(&args.input)?
        .map(|mut zone| {
            let insee = match zone.zone_type {
                Some(City) => admin::read_insee(&zone.tags).map(|s| s.to_owned()),
                _ => None,
            };
            cosmogony_id_to_osm_id.insert(zone.id.clone(), (zone.osm_id.clone(), insee));
            zone.boundary = None; // to prevent too much memory consumption
            zone
        })
        .collect::<Vec<_>>();

    let admins_without_boundaries = zones
        .into_iter()
        .map(|z| {
            let admin = z.into_admin(
                &cosmogony_id_to_osm_id,
                &args.langs,
                args.french_id_retrocompatibility,
                max_weight,
                None,
            );
            (admin.id.clone(), Arc::new(admin))
        })
        .collect::<HashMap<_, _>>();

    info!("importing cosmogony into Mimir");

    let admins = read_zones(&args.input)?.map(|z| {
        z.into_admin(
            &cosmogony_id_to_osm_id,
            &args.langs,
            args.french_id_retrocompatibility,
            max_weight,
            Some(&admins_without_boundaries),
        )
    });

    let index_settings = IndexSettings {
        nb_shards: args.nb_shards,
        nb_replicas: args.nb_replicas,
    };
    send_to_es(
        admins,
        &args.connection_string,
        &args.dataset,
        index_settings,
    )?;

    Ok(())
}

#[derive(StructOpt, Debug)]
struct Args {
    /// cosmogony file
    #[structopt(short = "i", long = "input")]
    input: String,
    /// Elasticsearch parameters.
    #[structopt(
        short = "c",
        long = "connection-string",
        default_value = "http://localhost:9200/munin"
    )]
    connection_string: String,
    /// Name of the dataset.
    #[structopt(short = "d", long = "dataset", default_value = "fr")]
    dataset: String,
    /// Number of shards for the es index
    #[structopt(short = "s", long = "nb-shards", default_value = "1")]
    nb_shards: usize,
    /// Number of replicas for the es index
    #[structopt(short = "r", long = "nb-replicas", default_value = "1")]
    nb_replicas: usize,
    /// Languages codes, used to build i18n names and labels
    #[structopt(name = "lang", short, long)]
    langs: Vec<String>,
    /// Retrocompatibiilty on french admin id
    /// if activated, the french administrative regions will have an id like 'admin:fr:{insee}'
    /// instead of 'admin:osm:{osm_id}'
    #[structopt(long = "french-id-retrocompatibility")]
    french_id_retrocompatibility: bool,
}

fn main() {
    mimirsbrunn::utils::launch_run(index_cosmogony);
}
<commit_msg>reduce memory usage of cosmogony2mimir<commit_after>// Copyright © 2018, 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 cosmogony::{Zone, ZoneIndex};
use failure::Error;
use mimir::objects::Admin;
use mimir::rubber::{IndexSettings, Rubber};
use mimirsbrunn::osm_reader::admin;
use mimirsbrunn::osm_reader::osm_utils;
use mimirsbrunn::utils;
use slog_scope::{info, warn};
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::sync::Arc;
use structopt::StructOpt;

trait IntoAdmin {
    fn into_admin(
        self,
        _: &BTreeMap<ZoneIndex, (String, Option<String>)>,
        langs: &[String],
        retrocompat_on_french_id: bool,
        max_weight: f64,
        all_admins: Option<&HashMap<String, Arc<Admin>>>,
    ) -> Admin;
}

fn get_weight(tags: &osmpbfreader::Tags, center_tags: &osmpbfreader::Tags) -> f64 {
    // to have an admin weight we use the osm 'population' tag to priorize
    // the big zones over the small one.
    // Note: this tags is not often filled , so only some zones
    // will have a weight (but the main cities have it).
    tags.get("population")
        .and_then(|p| p.parse().ok())
        .or_else(|| center_tags.get("population")?.parse().ok())
        .unwrap_or(0.)
}

impl IntoAdmin for Zone {
    fn into_admin(
        self,
        zones_osm_id: &BTreeMap<ZoneIndex, (String, Option<String>)>,
        langs: &[String],
        french_id_retrocompatibility: bool,
        max_weight: f64,
        all_admins: Option<&HashMap<String, Arc<Admin>>>,
    ) -> Admin {
        let insee = admin::read_insee(&self.tags).map(|s| s.to_owned());
        let zip_codes = admin::read_zip_codes(&self.tags);
        let label = self.label;
        let weight = get_weight(&self.tags, &self.center_tags);
        let center = self.center.map_or(mimir::Coord::default(), |c| {
            mimir::Coord::new(c.lng(), c.lat())
        });
        let format_id = |id, insee| {
            // for retrocompatibity reasons, Navitia needs the
            // french admins to have an id with the insee for cities
            match insee {
                Some(insee) if french_id_retrocompatibility => format!("admin:fr:{}", insee),
                _ => format!("admin:osm:{}", id),
            }
        };
        let parent_osm_id = self
            .parent
            .and_then(|id| zones_osm_id.get(&id))
            .map(|(id, insee)| format_id(id, insee.as_ref()));
        let codes = osm_utils::get_osm_codes_from_tags(&self.tags);
        let mut admin = Admin {
            id: zones_osm_id
                .get(&self.id)
                .map(|(id, insee)| format_id(id, insee.as_ref()))
                .expect("unable to find zone id in zones_osm_id"),
            insee: insee.unwrap_or("".to_owned()),
            level: self.admin_level.unwrap_or(0),
            label: label,
            name: self.name,
            zip_codes: zip_codes,
            weight: utils::normalize_weight(weight, max_weight),
            bbox: self.bbox,
            boundary: self.boundary,
            coord: center.clone(),
            approx_coord: Some(center.into()),
            zone_type: self.zone_type,
            parent_id: parent_osm_id,
            // Note: Since we do not really attach an admin to its hierarchy, for the moment an admin only have it's own coutry code,
            // not the country code of it's country from the hierarchy
            // (so it has a country code mainly if it is a country)
            country_codes: utils::get_country_code(&codes).into_iter().collect(),
            codes: codes,
            names: osm_utils::get_names_from_tags(&self.tags, &langs),
            labels: self
                .international_labels
                .into_iter()
                .filter(|(k, _)| langs.contains(&k))
                .collect(),
            distance: None,
            context: None,
            administrative_regions: Vec::new(),
        };
        if let Some(ref admins) = all_admins {
            // Get a list of encompassing parent ids, which will be used as the get
            // administrative_regions.
            let mut parent_ids = Vec::new();
            let mut current = &admin;
            while current.parent_id.is_some() {
                parent_ids.push(current.parent_id.clone().unwrap());
                if let Some(par) = admins.get(parent_ids.last().unwrap()) {
                    current = par;
                } else {
                    break;
                }
            }
            admin.administrative_regions = parent_ids
                .into_iter()
                .filter_map(|a| admins.get(&a))
                .map(|x| Arc::clone(x))
                .collect::<Vec<_>>();
        }
        admin
    }
}

fn send_to_es(
    admins: impl Iterator<Item = Admin>,
    cnx_string: &str,
    dataset: &str,
    index_settings: IndexSettings,
) -> Result<(), Error> {
    let mut rubber = Rubber::new(cnx_string);
    rubber.initialize_templates()?;
    let nb_admins = rubber.public_index(dataset, &index_settings, admins)?;
    info!("{} admins added.", nb_admins);
    Ok(())
}

fn read_zones(input: &str) -> Result<impl Iterator<Item = Zone>, Error> {
    Ok(cosmogony::read_zones_from_file(input)?
        .filter_map(|r| r.map_err(|e| warn!("impossible to read zone: {}", e)).ok()))
}

fn index_cosmogony(args: Args) -> Result<(), Error> {
    info!("building maps");
    use cosmogony::ZoneType::City;

    let mut cosmogony_id_to_osm_id = BTreeMap::new();
    let max_weight = utils::ADMIN_MAX_WEIGHT;
    for z in read_zones(&args.input)? {
        let insee = match z.zone_type {
            Some(City) => admin::read_insee(&z.tags).map(|s| s.to_owned()),
            _ => None,
        };
        cosmogony_id_to_osm_id.insert(z.id.clone(), (z.osm_id.clone(), insee));
    }
    let cosmogony_id_to_osm_id = cosmogony_id_to_osm_id;

    info!("building admins hierarchy");
    let admins_without_boundaries = read_zones(&args.input)?
        .map(|mut z| {
            z.boundary = None;
            let admin = z.into_admin(
                &cosmogony_id_to_osm_id,
                &args.langs,
                args.french_id_retrocompatibility,
                max_weight,
                None,
            );
            (admin.id.clone(), Arc::new(admin))
        })
        .collect::<HashMap<_, _>>();

    info!("importing cosmogony into Mimir");

    let admins = read_zones(&args.input)?.map(|z| {
        z.into_admin(
            &cosmogony_id_to_osm_id,
            &args.langs,
            args.french_id_retrocompatibility,
            max_weight,
            Some(&admins_without_boundaries),
        )
    });

    let index_settings = IndexSettings {
        nb_shards: args.nb_shards,
        nb_replicas: args.nb_replicas,
    };
    send_to_es(
        admins,
        &args.connection_string,
        &args.dataset,
        index_settings,
    )?;

    Ok(())
}

#[derive(StructOpt, Debug)]
struct Args {
    /// cosmogony file
    #[structopt(short = "i", long = "input")]
    input: String,
    /// Elasticsearch parameters.
    #[structopt(
        short = "c",
        long = "connection-string",
        default_value = "http://localhost:9200/munin"
    )]
    connection_string: String,
    /// Name of the dataset.
    #[structopt(short = "d", long = "dataset", default_value = "fr")]
    dataset: String,
    /// Number of shards for the es index
    #[structopt(short = "s", long = "nb-shards", default_value = "1")]
    nb_shards: usize,
    /// Number of replicas for the es index
    #[structopt(short = "r", long = "nb-replicas", default_value = "1")]
    nb_replicas: usize,
    /// Languages codes, used to build i18n names and labels
    #[structopt(name = "lang", short, long)]
    langs: Vec<String>,
    /// Retrocompatibiilty on french admin id
    /// if activated, the french administrative regions will have an id like 'admin:fr:{insee}'
    /// instead of 'admin:osm:{osm_id}'
    #[structopt(long = "french-id-retrocompatibility")]
    french_id_retrocompatibility: bool,
}

fn main() {
    mimirsbrunn::utils::launch_run(index_cosmogony);
}
<|endoftext|>


<commit_before>use super::pool::Pool;
use iron::headers::ContentType;
use iron::prelude::*;
use iron::status::Status;
use prometheus::{
    opts, register_counter, register_int_counter, register_int_gauge, Encoder, IntCounter,
    IntGauge, TextEncoder, __register_gauge, register_int_counter_vec, IntCounterVec,
    __register_counter_vec, histogram_opts, register_histogram_vec, HistogramVec,
};
use std::time::{Duration, Instant};

lazy_static::lazy_static! {
    static ref QUEUED_CRATES_COUNT: IntGauge = register_int_gauge!(
        "docsrs_queued_crates_count",
        "Number of crates in the build queue"
    )
    .unwrap();

    pub static ref PRIORITIZED_CRATES_COUNT: IntGauge = register_int_gauge!(
        "docsrs_prioritized_crates_count",
        "Number of crates in the build queue that have a positive priority"
    )
    .unwrap();

    static ref FAILED_CRATES_COUNT: IntGauge = register_int_gauge!(
        "docsrs_failed_crates_count",
        "Number of crates that failed to build"
    )
    .unwrap();

    pub static ref TOTAL_BUILDS: IntCounter = register_int_counter!(
        "docsrs_total_builds",
        "Number of crates built"
    )
    .unwrap();

    pub static ref SUCCESSFUL_BUILDS: IntCounter = register_int_counter!(
        "docsrs_successful_builds",
        "Number of builds that successfully generated docs"
    )
    .unwrap();

    pub static ref FAILED_BUILDS: IntCounter = register_int_counter!(
        "docsrs_failed_builds",
        "Number of builds that generated a compile error"
    )
    .unwrap();

    pub static ref NON_LIBRARY_BUILDS: IntCounter = register_int_counter!(
        "docsrs_non_library_builds",
        "Number of builds that did not complete due to not being a library"
    )
    .unwrap();

    pub static ref UPLOADED_FILES_TOTAL: IntCounter = register_int_counter!(
        "docsrs_uploaded_files_total",
        "Number of files uploaded to S3 or stored in the database"
    )
    .unwrap();

    pub static ref ROUTES_VISITED: IntCounterVec = register_int_counter_vec!(
        "docsrs_routes_visited",
        "The traffic of various docs.rs routes",
        &["route"]
    )
    .unwrap();

    pub static ref RESPONSE_TIMES: HistogramVec = register_histogram_vec!(
        "docsrs_response_time",
        "The response times of various docs.rs routes",
        &["route"]
    )
    .unwrap();

    pub static ref FAILED_DB_CONNECTIONS: IntCounter = register_int_counter!(
        "docsrs_failed_db_connections",
        "Number of attempted and failed connections to the database"
    )
    .unwrap();

    pub static ref USED_DB_CONNECTIONS: IntGauge = register_int_gauge!(
        "docsrs_used_db_connections",
        "The number of used database connections"
    )
    .unwrap();

    pub static ref IDLE_DB_CONNECTIONS: IntGauge = register_int_gauge!(
        "docsrs_idle_db_connections",
        "The number of idle database connections"
    )
    .unwrap();

    pub static ref OPEN_FILE_DESCRIPTORS: IntGauge = register_int_gauge!(
        "docsrs_open_file_descriptors",
        "The number of currently opened file descriptors"
    )
    .unwrap();

    pub static ref CURRENTLY_RUNNING_THREADS: IntGauge = register_int_gauge!(
        "docsrs_running_threads",
        "The number of threads being used by docs.rs"
    )
    .unwrap();
}

pub fn metrics_handler(req: &mut Request) -> IronResult<Response> {
    {
        let pool = extension!(req, Pool);
        let conn = pool.get()?;

        USED_DB_CONNECTIONS.set(pool.used_connections() as i64);
        IDLE_DB_CONNECTIONS.set(pool.idle_connections() as i64);

        QUEUED_CRATES_COUNT.set(
            ctry!(conn.query("SELECT COUNT(*) FROM queue WHERE attempt < 5;", &[]))
                .get(0)
                .get(0),
        );
        PRIORITIZED_CRATES_COUNT.set(
            ctry!(conn.query("SELECT COUNT(*) FROM queue WHERE priority >= 0;", &[]))
                .get(0)
                .get(0),
        );
        FAILED_CRATES_COUNT.set(
            ctry!(conn.query("SELECT COUNT(*) FROM queue WHERE attempt >= 5;", &[]))
                .get(0)
                .get(0),
        );
    }

    #[cfg(not(windows))]
    {
        use procfs::process::Process;

        let process = Process::myself().unwrap();
        OPEN_FILE_DESCRIPTORS.set(process.fd().unwrap().len() as i64);
        CURRENTLY_RUNNING_THREADS.set(process.stat().unwrap().num_threads as i64);
    }

    let mut buffer = Vec::new();
    let families = prometheus::gather();
    ctry!(TextEncoder::new().encode(&families, &mut buffer));

    let mut resp = Response::with(buffer);
    resp.status = Some(Status::Ok);
    resp.headers
        .set(ContentType("text/plain; version=0.0.4".parse().unwrap()));
    Ok(resp)
}

/// Converts a `Duration` to seconds, used by prometheus internally
#[inline]
fn duration_to_seconds(d: Duration) -> f64 {
    let nanos = f64::from(d.subsec_nanos()) / 1e9;
    d.as_secs() as f64 + nanos
}

pub struct RequestRecorder {
    handler: Box<dyn iron::Handler>,
    route_name: String,
}

impl RequestRecorder {
    pub fn new(handler: impl iron::Handler, route: impl Into<String>) -> Self {
        Self {
            handler: Box::new(handler),
            route_name: route.into(),
        }
    }
}

impl iron::Handler for RequestRecorder {
    fn handle(&self, request: &mut Request) -> IronResult<Response> {
        let start = Instant::now();
        let result = self.handler.handle(request);
        let resp_time = duration_to_seconds(start.elapsed());

        ROUTES_VISITED.with_label_values(&[&self.route_name]).inc();
        RESPONSE_TIMES
            .with_label_values(&[&self.route_name])
            .observe(resp_time);

        #[cfg(test)]
        tests::record_tests(&self.route_name);

        result
    }
}

#[cfg(test)]
mod tests {
    use crate::test::wrapper;
    use std::{
        collections::HashMap,
        sync::{
            atomic::{AtomicUsize, Ordering},
            Mutex,
        },
    };

    static ROUTES_VISITED: AtomicUsize = AtomicUsize::new(0);
    lazy_static::lazy_static! {
        static ref RESPONSE_TIMES: Mutex<HashMap<String, usize>> = Mutex::new(HashMap::new());
    }

    pub fn record_tests(route: &str) {
        ROUTES_VISITED.fetch_add(1, Ordering::SeqCst);

        let mut times = RESPONSE_TIMES.lock().unwrap();
        if let Some(requests) = times.get_mut(route) {
            *requests += 1;
        } else {
            times.insert(route.to_owned(), 1);
        }
    }

    fn reset_records() {
        ROUTES_VISITED.store(0, Ordering::SeqCst);
        RESPONSE_TIMES.lock().unwrap().clear();
    }

    #[test]
    fn home_page() {
        wrapper(|env| {
            let frontend = env.frontend();

            reset_records();

            frontend.get("/").send()?;
            frontend.get("/").send()?;
            assert_eq!(ROUTES_VISITED.load(Ordering::SeqCst), 2);
            assert_eq!(RESPONSE_TIMES.lock().unwrap().get("/"), Some(&2));

            reset_records();

            frontend.get("").send()?;
            frontend.get("").send()?;
            assert_eq!(ROUTES_VISITED.load(Ordering::SeqCst), 2);
            assert_eq!(RESPONSE_TIMES.lock().unwrap().get("/"), Some(&2));

            Ok(())
        })
    }

    #[test]
    fn resources() {
        wrapper(|env| {
            let frontend = env.frontend();

            let routes = [
                "/style.css",
                "/index.js",
                "/menu.js",
                "/sitemap.xml",
                "/opensearch.xml",
                "/robots.txt",
            ];

            for route in routes.iter() {
                reset_records();

                frontend.get(route).send()?;
                frontend.get(route).send()?;

                assert_eq!(ROUTES_VISITED.load(Ordering::SeqCst), 2);
                assert_eq!(
                    RESPONSE_TIMES.lock().unwrap().get("static resource"),
                    Some(&2)
                );
            }

            Ok(())
        })
    }

    #[test]
    fn releases() {
        wrapper(|env| {
            env.db()
                .fake_release()
                .name("rcc")
                .version("0.0.0")
                .repo("https://github.com/jyn514/rcc")
                .create()?;
            env.db()
                .fake_release()
                .name("rcc")
                .version("1.0.0")
                .build_result_successful(false)
                .create()?;

            let frontend = env.frontend();

            let routes = [
                ("/releases", "/releases"),
                ("/releases/recent/1", "/releases/recent/:page"),
                ("/releases/feed", "static resource"),
                ("/releases/queue", "/releases/queue"),
                ("/releases/recent-failures", "/releases/recent-failures"),
                (
                    "/releases/recent-failures/1",
                    "/releases/recent-failures/:page",
                ),
            ];

            for (route, correct) in routes.iter() {
                reset_records();

                frontend.get(route).send()?;
                frontend.get(route).send()?;

                assert_eq!(ROUTES_VISITED.load(Ordering::SeqCst), 2);
                assert_eq!(RESPONSE_TIMES.lock().unwrap().get(*correct), Some(&2));
            }

            Ok(())
        })
    }

    #[test]
    fn crates() {
        wrapper(|env| {
            env.db()
                .fake_release()
                .name("rcc")
                .version("0.0.0")
                .create()?;
            env.db()
                .fake_release()
                .name("hexponent")
                .version("0.2.0")
                .create()?;

            let frontend = env.frontend();

            let routes = ["/crate/rcc/0.0.0", "/crate/hexponent/0.2.0"];

            for route in routes.iter() {
                reset_records();

                frontend.get(route).send()?;
                frontend.get(route).send()?;

                assert_eq!(ROUTES_VISITED.load(Ordering::SeqCst), 2);
                assert_eq!(
                    RESPONSE_TIMES.lock().unwrap().get("/crate/:name/:version"),
                    Some(&2)
                );
            }

            Ok(())
        })
    }
}
<commit_msg>Added resoning to scoping db calls<commit_after>use super::pool::Pool;
use iron::headers::ContentType;
use iron::prelude::*;
use iron::status::Status;
use prometheus::{
    opts, register_counter, register_int_counter, register_int_gauge, Encoder, IntCounter,
    IntGauge, TextEncoder, __register_gauge, register_int_counter_vec, IntCounterVec,
    __register_counter_vec, histogram_opts, register_histogram_vec, HistogramVec,
};
use std::time::{Duration, Instant};

lazy_static::lazy_static! {
    static ref QUEUED_CRATES_COUNT: IntGauge = register_int_gauge!(
        "docsrs_queued_crates_count",
        "Number of crates in the build queue"
    )
    .unwrap();

    pub static ref PRIORITIZED_CRATES_COUNT: IntGauge = register_int_gauge!(
        "docsrs_prioritized_crates_count",
        "Number of crates in the build queue that have a positive priority"
    )
    .unwrap();

    static ref FAILED_CRATES_COUNT: IntGauge = register_int_gauge!(
        "docsrs_failed_crates_count",
        "Number of crates that failed to build"
    )
    .unwrap();

    pub static ref TOTAL_BUILDS: IntCounter = register_int_counter!(
        "docsrs_total_builds",
        "Number of crates built"
    )
    .unwrap();

    pub static ref SUCCESSFUL_BUILDS: IntCounter = register_int_counter!(
        "docsrs_successful_builds",
        "Number of builds that successfully generated docs"
    )
    .unwrap();

    pub static ref FAILED_BUILDS: IntCounter = register_int_counter!(
        "docsrs_failed_builds",
        "Number of builds that generated a compile error"
    )
    .unwrap();

    pub static ref NON_LIBRARY_BUILDS: IntCounter = register_int_counter!(
        "docsrs_non_library_builds",
        "Number of builds that did not complete due to not being a library"
    )
    .unwrap();

    pub static ref UPLOADED_FILES_TOTAL: IntCounter = register_int_counter!(
        "docsrs_uploaded_files_total",
        "Number of files uploaded to S3 or stored in the database"
    )
    .unwrap();

    pub static ref ROUTES_VISITED: IntCounterVec = register_int_counter_vec!(
        "docsrs_routes_visited",
        "The traffic of various docs.rs routes",
        &["route"]
    )
    .unwrap();

    pub static ref RESPONSE_TIMES: HistogramVec = register_histogram_vec!(
        "docsrs_response_time",
        "The response times of various docs.rs routes",
        &["route"]
    )
    .unwrap();

    pub static ref FAILED_DB_CONNECTIONS: IntCounter = register_int_counter!(
        "docsrs_failed_db_connections",
        "Number of attempted and failed connections to the database"
    )
    .unwrap();

    pub static ref USED_DB_CONNECTIONS: IntGauge = register_int_gauge!(
        "docsrs_used_db_connections",
        "The number of used database connections"
    )
    .unwrap();

    pub static ref IDLE_DB_CONNECTIONS: IntGauge = register_int_gauge!(
        "docsrs_idle_db_connections",
        "The number of idle database connections"
    )
    .unwrap();

    pub static ref OPEN_FILE_DESCRIPTORS: IntGauge = register_int_gauge!(
        "docsrs_open_file_descriptors",
        "The number of currently opened file descriptors"
    )
    .unwrap();

    pub static ref CURRENTLY_RUNNING_THREADS: IntGauge = register_int_gauge!(
        "docsrs_running_threads",
        "The number of threads being used by docs.rs"
    )
    .unwrap();
}

pub fn metrics_handler(req: &mut Request) -> IronResult<Response> {
    // Database calls are scoped in order to minimize the time a db connection is locked
    {
        let pool = extension!(req, Pool);
        let conn = pool.get()?;

        USED_DB_CONNECTIONS.set(pool.used_connections() as i64);
        IDLE_DB_CONNECTIONS.set(pool.idle_connections() as i64);

        QUEUED_CRATES_COUNT.set(
            ctry!(conn.query("SELECT COUNT(*) FROM queue WHERE attempt < 5;", &[]))
                .get(0)
                .get(0),
        );
        PRIORITIZED_CRATES_COUNT.set(
            ctry!(conn.query("SELECT COUNT(*) FROM queue WHERE priority >= 0;", &[]))
                .get(0)
                .get(0),
        );
        FAILED_CRATES_COUNT.set(
            ctry!(conn.query("SELECT COUNT(*) FROM queue WHERE attempt >= 5;", &[]))
                .get(0)
                .get(0),
        );
    }

    #[cfg(not(windows))]
    {
        use procfs::process::Process;

        let process = Process::myself().unwrap();
        OPEN_FILE_DESCRIPTORS.set(process.fd().unwrap().len() as i64);
        CURRENTLY_RUNNING_THREADS.set(process.stat().unwrap().num_threads as i64);
    }

    let mut buffer = Vec::new();
    let families = prometheus::gather();
    ctry!(TextEncoder::new().encode(&families, &mut buffer));

    let mut resp = Response::with(buffer);
    resp.status = Some(Status::Ok);
    resp.headers
        .set(ContentType("text/plain; version=0.0.4".parse().unwrap()));
    Ok(resp)
}

/// Converts a `Duration` to seconds, used by prometheus internally
#[inline]
fn duration_to_seconds(d: Duration) -> f64 {
    let nanos = f64::from(d.subsec_nanos()) / 1e9;
    d.as_secs() as f64 + nanos
}

pub struct RequestRecorder {
    handler: Box<dyn iron::Handler>,
    route_name: String,
}

impl RequestRecorder {
    pub fn new(handler: impl iron::Handler, route: impl Into<String>) -> Self {
        Self {
            handler: Box::new(handler),
            route_name: route.into(),
        }
    }
}

impl iron::Handler for RequestRecorder {
    fn handle(&self, request: &mut Request) -> IronResult<Response> {
        let start = Instant::now();
        let result = self.handler.handle(request);
        let resp_time = duration_to_seconds(start.elapsed());

        ROUTES_VISITED.with_label_values(&[&self.route_name]).inc();
        RESPONSE_TIMES
            .with_label_values(&[&self.route_name])
            .observe(resp_time);

        #[cfg(test)]
        tests::record_tests(&self.route_name);

        result
    }
}

#[cfg(test)]
mod tests {
    use crate::test::wrapper;
    use std::{
        collections::HashMap,
        sync::{
            atomic::{AtomicUsize, Ordering},
            Mutex,
        },
    };

    static ROUTES_VISITED: AtomicUsize = AtomicUsize::new(0);
    lazy_static::lazy_static! {
        static ref RESPONSE_TIMES: Mutex<HashMap<String, usize>> = Mutex::new(HashMap::new());
    }

    pub fn record_tests(route: &str) {
        ROUTES_VISITED.fetch_add(1, Ordering::SeqCst);

        let mut times = RESPONSE_TIMES.lock().unwrap();
        if let Some(requests) = times.get_mut(route) {
            *requests += 1;
        } else {
            times.insert(route.to_owned(), 1);
        }
    }

    fn reset_records() {
        ROUTES_VISITED.store(0, Ordering::SeqCst);
        RESPONSE_TIMES.lock().unwrap().clear();
    }

    #[test]
    fn home_page() {
        wrapper(|env| {
            let frontend = env.frontend();

            reset_records();

            frontend.get("/").send()?;
            frontend.get("/").send()?;
            assert_eq!(ROUTES_VISITED.load(Ordering::SeqCst), 2);
            assert_eq!(RESPONSE_TIMES.lock().unwrap().get("/"), Some(&2));

            reset_records();

            frontend.get("").send()?;
            frontend.get("").send()?;
            assert_eq!(ROUTES_VISITED.load(Ordering::SeqCst), 2);
            assert_eq!(RESPONSE_TIMES.lock().unwrap().get("/"), Some(&2));

            Ok(())
        })
    }

    #[test]
    fn resources() {
        wrapper(|env| {
            let frontend = env.frontend();

            let routes = [
                "/style.css",
                "/index.js",
                "/menu.js",
                "/sitemap.xml",
                "/opensearch.xml",
                "/robots.txt",
            ];

            for route in routes.iter() {
                reset_records();

                frontend.get(route).send()?;
                frontend.get(route).send()?;

                assert_eq!(ROUTES_VISITED.load(Ordering::SeqCst), 2);
                assert_eq!(
                    RESPONSE_TIMES.lock().unwrap().get("static resource"),
                    Some(&2)
                );
            }

            Ok(())
        })
    }

    #[test]
    fn releases() {
        wrapper(|env| {
            env.db()
                .fake_release()
                .name("rcc")
                .version("0.0.0")
                .repo("https://github.com/jyn514/rcc")
                .create()?;
            env.db()
                .fake_release()
                .name("rcc")
                .version("1.0.0")
                .build_result_successful(false)
                .create()?;

            let frontend = env.frontend();

            let routes = [
                ("/releases", "/releases"),
                ("/releases/recent/1", "/releases/recent/:page"),
                ("/releases/feed", "static resource"),
                ("/releases/queue", "/releases/queue"),
                ("/releases/recent-failures", "/releases/recent-failures"),
                (
                    "/releases/recent-failures/1",
                    "/releases/recent-failures/:page",
                ),
            ];

            for (route, correct) in routes.iter() {
                reset_records();

                frontend.get(route).send()?;
                frontend.get(route).send()?;

                assert_eq!(ROUTES_VISITED.load(Ordering::SeqCst), 2);
                assert_eq!(RESPONSE_TIMES.lock().unwrap().get(*correct), Some(&2));
            }

            Ok(())
        })
    }

    #[test]
    fn crates() {
        wrapper(|env| {
            env.db()
                .fake_release()
                .name("rcc")
                .version("0.0.0")
                .create()?;
            env.db()
                .fake_release()
                .name("hexponent")
                .version("0.2.0")
                .create()?;

            let frontend = env.frontend();

            let routes = ["/crate/rcc/0.0.0", "/crate/hexponent/0.2.0"];

            for route in routes.iter() {
                reset_records();

                frontend.get(route).send()?;
                frontend.get(route).send()?;

                assert_eq!(ROUTES_VISITED.load(Ordering::SeqCst), 2);
                assert_eq!(
                    RESPONSE_TIMES.lock().unwrap().get("/crate/:name/:version"),
                    Some(&2)
                );
            }

            Ok(())
        })
    }
}
<|endoftext|>


<commit_before>#![allow(non_camel_case_types, non_snake_case)]
extern crate winreg;

use pdcurses::*;
use libc::c_int;
use std::ffi::CString;
use std::i32;
use self::winreg::RegKey;
use windows::winreg::enums::HKEY_CURRENT_USER;

use std::env;
use std::path::Path;
use std::ffi::OsStr;

pub mod constants;
use self::constants::*;

use input::Input;

pub fn pre_init() {
    #[cfg(any(feature = "win32a", all(not(feature = "win32"), not(feature = "win32a"))))]
    win32a_pre_init();
}

#[cfg(any(feature = "win32a", all(not(feature = "win32"), not(feature = "win32a"))))]
fn win32a_pre_init() {

    let exe_name = env::current_exe()
        .ok()
        .as_ref()
        .map(Path::new)
        .and_then(Path::file_name)
        .and_then(OsStr::to_str)
        .map(String::from);

    if exe_name.is_none() {
        warn!("Could not determine name of exe");
        return;
    }
    let exe_name = exe_name.unwrap();

    // The format of the registry value is:
    // <cols>x<rows>,<font_size>,<x_loc>,<y_loc>,<menu_shown>;<min_lines>,<max_lines>,<min_cols>,<max_cols>:<font_name>
    // Example: 80x25,12,312,312,1;2,2147483647,2,2147483647:Courier New
    let hkcu = RegKey::predef(HKEY_CURRENT_USER);
    let existing_value = hkcu.open_subkey("Software\\PDCurses")
        .and_then(|reg_key| reg_key.get_value::<String, &str>(&exe_name))
        .unwrap_or("80x25,12,0,0,1;25,25,80,80:Courier New".to_string());

    let rejoined_menu: String;
    let maxi32 = format!("{}", i32::MAX);
    let mut split: Vec<&str> = existing_value.split(',').collect();

    // The value at index 4 will be <menu_shown>;<min_lines> because we split on ,
    let mut menu_split: Vec<&str> = split[4].split(';').collect();

    // Set the menu_shown value to 0 to always hide it
    #[cfg(not(feature = "show_menu"))]
    {
        menu_split[0] = "0";
    }

    #[cfg(not(feature = "disable_resize"))]
    {
        // Set the resize limits to 2 - i32::MAX to allow unlimited resizing
        menu_split[1] = "2";
        split[5] = &maxi32;
        split[6] = "2";
        split[7] = &maxi32;
    }

    // Re-join the values back into a string
    rejoined_menu = menu_split.join(";");
    split[4] = &rejoined_menu;

    // Write the modified values back into the registry
    match hkcu.open_subkey("Software\\PDCurses").and_then(|reg_key| {
        reg_key.set_value::<String, &str>(&exe_name, &split.join(","))
    }) {
        Err(e) => warn!("Failed to set registry value: {}", e),
        _ => (),
    }
}

pub fn _attron(w: *mut WINDOW, attributes: chtype) -> i32 {
    unsafe { wattron(w, attributes) }
}

pub fn _attroff(w: *mut WINDOW, attributes: chtype) -> i32 {
    unsafe { wattroff(w, attributes) }
}

pub fn _attrset(w: *mut WINDOW, attributes: chtype) -> i32 {
    unsafe { wattrset(w, attributes) }
}

pub fn _COLORS() -> i32 {
    unsafe { COLORS }
}

pub fn _COLOR_PAIRS() -> i32 {
    unsafe { COLOR_PAIRS }
}

pub fn _draw_box(w: *mut WINDOW, verch: chtype, horch: chtype) -> i32 {
    unsafe { _box(w, verch, horch) }
}

pub fn _getmouse() -> Result<MEVENT, i32> {
    let mut mevent = MEVENT {
        id: 0,
        x: 0,
        y: 0,
        z: 0,
        bstate: 0,
    };
    let error = unsafe { nc_getmouse(&mut mevent) };
    if error == 0 { Ok(mevent) } else { Err(error) }
}

pub fn _mouse_trafo(w: &mut *mut WINDOW, y: &mut i32, x: &mut i32, to_screen: bool) {
    unsafe {
        wmouse_trafo(*w, y, x, to_screen as u8);
    }
}

pub fn _resize_term(nlines: i32, ncols: i32) -> i32 {
    unsafe { resize_term(nlines, ncols) }
}

pub fn _set_blink(enabled: bool) -> i32 {
    unsafe { PDC_set_blink(enabled as u8) }
}

pub fn _set_title(title: &str) {
    let s = CString::new(title).unwrap();
    unsafe { PDC_set_title(s.as_ptr()) }
}

/// Converts an integer returned by getch() to a Input value
pub fn to_special_keycode(i: i32) -> Input {
    // Not the best, but until I add _all_ the keys between UNDO and RESIZE this will have to do
    // Most of them are PDCurses specific anyway and would not map to Input if I want to keep it
    // clean of implementation specific keys.
    if i == KEY_RESIZE {
        Input::KeyResize
    } else if i == KEY_MOUSE {
        Input::KeyMouse
    } else {
        assert!(
            i >= KEY_OFFSET,
            format!("Input value less than expected: {:?}", i)
        );
        let i = if i <= KEY_F15 {
            i - KEY_OFFSET
        } else {
            i - KEY_OFFSET - 48
        };
        if i as usize >= SPECIAL_KEY_CODES.len() {
            Input::Unknown(i)
        } else {
            SPECIAL_KEY_CODES[i as usize]
        }
    }
}

pub fn _ungetch(input: &Input) -> i32 {
    let i = convert_input_to_c_int(input);
    unsafe { PDC_ungetch(i) }
}

fn convert_input_to_c_int(input: &Input) -> c_int {
    match *input {
        Input::Character(c) => c as c_int,
        Input::Unknown(i) => i,
        Input::KeyResize => KEY_RESIZE,
        Input::KeyMouse => KEY_MOUSE,
        specialKeyCode => {
            for (i, skc) in SPECIAL_KEY_CODES.into_iter().enumerate() {
                if *skc == specialKeyCode {
                    let result = i as c_int + KEY_OFFSET;
                    if result <= KEY_F15 {
                        return result;
                    } else {
                        return result + 48;
                    }
                }
            }
            panic!("Failed to convert Input back to a c_int");
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use super::convert_input_to_c_int;
    use input::Input;
    use libc::c_int;

    #[test]
    fn test_key_dl_to_special_keycode() {
        assert_eq!(Input::KeyDL, to_special_keycode(KEY_OFFSET + 0x48));
    }

    #[test]
    fn test_key_f15_to_input() {
        assert_eq!(Input::KeyF15, to_special_keycode(KEY_OFFSET + 0x08 + 15));
    }

    #[test]
    fn test_key_up_to_input() {
        assert_eq!(Input::KeyUp, to_special_keycode(KEY_OFFSET + 3));
    }

    #[test]
    fn test_convert_input_to_c_int() {
        let i = convert_input_to_c_int(&Input::Character('a'));
        assert_eq!('a' as c_int, i);
    }

    #[test]
    fn test_convert_backspace_to_c_int() {
        let i = convert_input_to_c_int(&Input::KeyBackspace);
        assert_eq!(KEY_OFFSET + 0x07, i);
    }

    #[test]
    fn test_convert_sdl_to_c_int() {
        let i = convert_input_to_c_int(&Input::KeySDL);
        assert_eq!(KEY_OFFSET + 0x7e, i);
    }

    #[test]
    fn test_convert_key_mouse() {
        let i = convert_input_to_c_int(&Input::KeyMouse);
        let kc = to_special_keycode(i);
        assert_eq!(i, convert_input_to_c_int(&kc));
    }
}
<commit_msg>Reorganized use's<commit_after>#![allow(non_camel_case_types, non_snake_case)]
extern crate winreg;

use pdcurses::*;
use libc::c_int;

use std::env;
use std::ffi::{CString, OsStr};
use std::i32;
use std::path::Path;

use self::winreg::RegKey;
use windows::winreg::enums::HKEY_CURRENT_USER;

pub mod constants;
use self::constants::*;

use input::Input;

pub fn pre_init() {
    #[cfg(any(feature = "win32a", all(not(feature = "win32"), not(feature = "win32a"))))]
    win32a_pre_init();
}

#[cfg(any(feature = "win32a", all(not(feature = "win32"), not(feature = "win32a"))))]
fn win32a_pre_init() {

    let exe_name = env::current_exe()
        .ok()
        .as_ref()
        .map(Path::new)
        .and_then(Path::file_name)
        .and_then(OsStr::to_str)
        .map(String::from);

    if exe_name.is_none() {
        warn!("Could not determine name of exe");
        return;
    }
    let exe_name = exe_name.unwrap();

    // The format of the registry value is:
    // <cols>x<rows>,<font_size>,<x_loc>,<y_loc>,<menu_shown>;<min_lines>,<max_lines>,<min_cols>,<max_cols>:<font_name>
    // Example: 80x25,12,312,312,1;2,2147483647,2,2147483647:Courier New
    let hkcu = RegKey::predef(HKEY_CURRENT_USER);
    let existing_value = hkcu.open_subkey("Software\\PDCurses")
        .and_then(|reg_key| reg_key.get_value::<String, &str>(&exe_name))
        .unwrap_or("80x25,12,0,0,1;25,25,80,80:Courier New".to_string());

    let rejoined_menu: String;
    let maxi32 = format!("{}", i32::MAX);
    let mut split: Vec<&str> = existing_value.split(',').collect();

    // The value at index 4 will be <menu_shown>;<min_lines> because we split on ,
    let mut menu_split: Vec<&str> = split[4].split(';').collect();

    // Set the menu_shown value to 0 to always hide it
    #[cfg(not(feature = "show_menu"))]
    {
        menu_split[0] = "0";
    }

    #[cfg(not(feature = "disable_resize"))]
    {
        // Set the resize limits to 2 - i32::MAX to allow unlimited resizing
        menu_split[1] = "2";
        split[5] = &maxi32;
        split[6] = "2";
        split[7] = &maxi32;
    }

    // Re-join the values back into a string
    rejoined_menu = menu_split.join(";");
    split[4] = &rejoined_menu;

    // Write the modified values back into the registry
    match hkcu.open_subkey("Software\\PDCurses").and_then(|reg_key| {
        reg_key.set_value::<String, &str>(&exe_name, &split.join(","))
    }) {
        Err(e) => warn!("Failed to set registry value: {}", e),
        _ => (),
    }
}

pub fn _attron(w: *mut WINDOW, attributes: chtype) -> i32 {
    unsafe { wattron(w, attributes) }
}

pub fn _attroff(w: *mut WINDOW, attributes: chtype) -> i32 {
    unsafe { wattroff(w, attributes) }
}

pub fn _attrset(w: *mut WINDOW, attributes: chtype) -> i32 {
    unsafe { wattrset(w, attributes) }
}

pub fn _COLORS() -> i32 {
    unsafe { COLORS }
}

pub fn _COLOR_PAIRS() -> i32 {
    unsafe { COLOR_PAIRS }
}

pub fn _draw_box(w: *mut WINDOW, verch: chtype, horch: chtype) -> i32 {
    unsafe { _box(w, verch, horch) }
}

pub fn _getmouse() -> Result<MEVENT, i32> {
    let mut mevent = MEVENT {
        id: 0,
        x: 0,
        y: 0,
        z: 0,
        bstate: 0,
    };
    let error = unsafe { nc_getmouse(&mut mevent) };
    if error == 0 { Ok(mevent) } else { Err(error) }
}

pub fn _mouse_trafo(w: &mut *mut WINDOW, y: &mut i32, x: &mut i32, to_screen: bool) {
    unsafe {
        wmouse_trafo(*w, y, x, to_screen as u8);
    }
}

pub fn _resize_term(nlines: i32, ncols: i32) -> i32 {
    unsafe { resize_term(nlines, ncols) }
}

pub fn _set_blink(enabled: bool) -> i32 {
    unsafe { PDC_set_blink(enabled as u8) }
}

pub fn _set_title(title: &str) {
    let s = CString::new(title).unwrap();
    unsafe { PDC_set_title(s.as_ptr()) }
}

/// Converts an integer returned by getch() to a Input value
pub fn to_special_keycode(i: i32) -> Input {
    // Not the best, but until I add _all_ the keys between UNDO and RESIZE this will have to do
    // Most of them are PDCurses specific anyway and would not map to Input if I want to keep it
    // clean of implementation specific keys.
    if i == KEY_RESIZE {
        Input::KeyResize
    } else if i == KEY_MOUSE {
        Input::KeyMouse
    } else {
        assert!(
            i >= KEY_OFFSET,
            format!("Input value less than expected: {:?}", i)
        );
        let i = if i <= KEY_F15 {
            i - KEY_OFFSET
        } else {
            i - KEY_OFFSET - 48
        };
        if i as usize >= SPECIAL_KEY_CODES.len() {
            Input::Unknown(i)
        } else {
            SPECIAL_KEY_CODES[i as usize]
        }
    }
}

pub fn _ungetch(input: &Input) -> i32 {
    let i = convert_input_to_c_int(input);
    unsafe { PDC_ungetch(i) }
}

fn convert_input_to_c_int(input: &Input) -> c_int {
    match *input {
        Input::Character(c) => c as c_int,
        Input::Unknown(i) => i,
        Input::KeyResize => KEY_RESIZE,
        Input::KeyMouse => KEY_MOUSE,
        specialKeyCode => {
            for (i, skc) in SPECIAL_KEY_CODES.into_iter().enumerate() {
                if *skc == specialKeyCode {
                    let result = i as c_int + KEY_OFFSET;
                    if result <= KEY_F15 {
                        return result;
                    } else {
                        return result + 48;
                    }
                }
            }
            panic!("Failed to convert Input back to a c_int");
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use super::convert_input_to_c_int;
    use input::Input;
    use libc::c_int;

    #[test]
    fn test_key_dl_to_special_keycode() {
        assert_eq!(Input::KeyDL, to_special_keycode(KEY_OFFSET + 0x48));
    }

    #[test]
    fn test_key_f15_to_input() {
        assert_eq!(Input::KeyF15, to_special_keycode(KEY_OFFSET + 0x08 + 15));
    }

    #[test]
    fn test_key_up_to_input() {
        assert_eq!(Input::KeyUp, to_special_keycode(KEY_OFFSET + 3));
    }

    #[test]
    fn test_convert_input_to_c_int() {
        let i = convert_input_to_c_int(&Input::Character('a'));
        assert_eq!('a' as c_int, i);
    }

    #[test]
    fn test_convert_backspace_to_c_int() {
        let i = convert_input_to_c_int(&Input::KeyBackspace);
        assert_eq!(KEY_OFFSET + 0x07, i);
    }

    #[test]
    fn test_convert_sdl_to_c_int() {
        let i = convert_input_to_c_int(&Input::KeySDL);
        assert_eq!(KEY_OFFSET + 0x7e, i);
    }

    #[test]
    fn test_convert_key_mouse() {
        let i = convert_input_to_c_int(&Input::KeyMouse);
        let kc = to_special_keycode(i);
        assert_eq!(i, convert_input_to_c_int(&kc));
    }
}
<|endoftext|>


<commit_before>use engine::Game;
use gui::{Console, Key, Menu};
use gui::screens::{Screen, ScreenChange};
use util::units::Point;

use std::fmt;

pub struct PauseScreen {
    menu: Menu<MenuItem>,
}

enum MenuItem {
    Resume,
    Exit,
}

impl fmt::Display for MenuItem {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            MenuItem::Resume => write!(fmt, "Resume"),
            MenuItem::Exit => write!(fmt, "Exit Game"),
        }
    }
}

impl PauseScreen {
    pub fn new() -> Box<Screen + 'static> {
        Box::new(
            PauseScreen {
                menu: Menu::new(vec![MenuItem::Resume, MenuItem::Exit]),
            }
        )
    }
}

impl Screen for PauseScreen {

    #[allow(unused)]
    fn input(&mut self, game: &mut Game, console: &mut Console) -> Option<ScreenChange> {
        if let Some(key) = console.check_for_keypress() {
            match key {
                Key::Up => {
                    self.menu.prev();
                }
                Key::Down => {
                    self.menu.next();
                }
                Key::Enter => {
                    match *self.menu.selected() {
                        MenuItem::Resume => return Some(ScreenChange::RemoveScreen),
                        MenuItem::Exit => return Some(ScreenChange::ExitGame),
                    }
                },
                Key::Escape => return Some(ScreenChange::RemoveScreen),
                _ => {}
            }
        }

        None
    }

    #[allow(unused)]
    fn update(&mut self, game: &mut Game, console: &mut Console) -> Option<ScreenChange> {
        None
    }

    #[allow(unused)]
    fn render(&mut self, game: &mut Game, console: &mut Console) {
        console.print_plain(Point::new(0, 0), "Paused");

        for (i, menu_item) in self.menu.enum_items() {
            console.print_plain(Point::new(20, 20) + (0, i as i32), format!("{}", menu_item).as_slice());
            if self.menu.is_selected(i) {
                console.put_plain(Point::new(20, 20) + (-2, i as i32), '*');
            }
        }
    }

}
<commit_msg>Clean up pause_screen rendering code<commit_after>use engine::Game;
use gui::{Console, Key, Menu};
use gui::screens::{Screen, ScreenChange};
use util::units::Point;

use std::fmt;

pub struct PauseScreen {
    menu: Menu<MenuItem>,
}

enum MenuItem {
    Resume,
    Exit,
}

impl fmt::Display for MenuItem {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            MenuItem::Resume => write!(fmt, "Resume"),
            MenuItem::Exit => write!(fmt, "Exit Game"),
        }
    }
}

impl PauseScreen {
    pub fn new() -> Box<Screen + 'static> {
        Box::new(
            PauseScreen {
                menu: Menu::new(vec![MenuItem::Resume, MenuItem::Exit]),
            }
        )
    }
}

impl Screen for PauseScreen {

    #[allow(unused)]
    fn input(&mut self, game: &mut Game, console: &mut Console) -> Option<ScreenChange> {
        if let Some(key) = console.check_for_keypress() {
            match key {
                Key::Up => {
                    self.menu.prev();
                }
                Key::Down => {
                    self.menu.next();
                }
                Key::Enter => {
                    match *self.menu.selected() {
                        MenuItem::Resume => return Some(ScreenChange::RemoveScreen),
                        MenuItem::Exit => return Some(ScreenChange::ExitGame),
                    }
                },
                Key::Escape => return Some(ScreenChange::RemoveScreen),
                _ => {}
            }
        }

        None
    }

    #[allow(unused)]
    fn update(&mut self, game: &mut Game, console: &mut Console) -> Option<ScreenChange> {
        None
    }

    #[allow(unused)]
    fn render(&mut self, game: &mut Game, console: &mut Console) {
        console.print_plain(Point::new(0, 0), "Paused");

        let menu_location = Point::new(20, 20);

        for (i, menu_item) in self.menu.enum_items() {
            console.print_plain(menu_location.down(i as i32).right(2), format!("{}", menu_item).as_slice());
            if self.menu.is_selected(i) {
                console.put_plain(menu_location.down(i as i32), '>');
            }
        }
    }

}
<|endoftext|>


<commit_before>//!
//! The lex tool generates lexer tools from its input
//!

use std::result::Result;
use serde::*;
use serde_json::*;
use silkthread_base::*;

///
/// Input for the lexer tool
///
#[derive(Serialize, Deserialize)]
pub struct LexToolInput {
    /// Name of the tool that the lexer will define
    pub new_tool_name:  String,

    /// The symbols that the lexer will match
    pub symbols:        LexToolSymbol
}

///
/// Lexer symbol
///
#[derive(Serialize, Deserialize)]
pub struct LexToolSymbol {
    /// The name of the symbol that will be generated if this match is made
    pub symbol_name:    String,

    /// The rule that will be matched against this symbol
    pub match_rule:     String
}

///
/// Lexer generation tool
///
pub struct LexTool {
}

impl LexTool {
    pub fn new() -> LexTool {
        LexTool { }
    }
}

impl Tool for LexTool {
    fn invoke_json(&self, input: Value, environment: &Environment) -> Result<Value, Value> {
        unimplemented!();
    }
}
<commit_msg>First pass at putting together a lexing tool generator<commit_after>//!
//! The lex tool generates lexer tools from its input
//!

use std::result::Result;
use std::error::Error;
use std::iter::*;
use serde::*;
use serde_json::*;
use concordance::*;
use silkthread_base::*;
use silkthread_base::basic::*;
use silkthread_base::basic::tool_name::*;

///
/// Input for the lexer tool
///
#[derive(Serialize, Deserialize)]
pub struct LexToolInput {
    /// Name of the tool that the lexer will define
    pub new_tool_name:  String,

    /// The symbols that the lexer will match
    pub symbols:        Vec<LexToolSymbol>
}

///
/// Represents a lexer match
///
#[derive(Serialize, Deserialize)]
pub struct LexerMatch {
    /// Token that was matched
    pub token:      String,

    /// Phrase that was matched from the input
    pub matched:    String,

    /// Start of the match
    pub start:      i32,

    /// End of the match
    pub end:        i32
}

///
/// Lexer symbol
///
#[derive(Serialize, Deserialize)]
pub struct LexToolSymbol {
    /// The name of the symbol that will be generated if this match is made
    pub symbol_name:    String,

    /// The rule that will be matched against this symbol
    pub match_rule:     String
}

///
/// Lexer generation tool
///
pub struct LexTool {
}

impl LexTool {
    ///
    /// Creates a new lexer tool
    ///
    pub fn new() -> LexTool {
        LexTool { }
    }

    ///
    /// Converts a string containing a lexer regex into a concordance pattern
    ///
    pub fn pattern_for_string(regex: &str) -> Pattern<char> {
        // We'll process the regex as UTF-16 code points
        let regex_chars: Vec<char> = regex.chars().collect();

        // Go on to build the pattern
        LexTool::pattern_for_chars(&regex_chars)
    }

    ///
    /// Returns a substitute character for a character following a '\'
    ///
    fn special_character_char(c: char) -> char {
        match c {
            'n' => '\n',
            'r' => '\r',
            't' => '\t',
            '\\' => '\\',
            'w' => ' ',

            // Just the literal character if there's no match
            c => c
        }
    }

    ///
    /// Returns the pattern to use for a special character
    ///
    fn special_character_pattern(c: char) -> Pattern<char> {
        match c {
            // Any whitespace
            'w' => MatchAny(vec![
                Match(vec![' ']),
                Match(vec!['\t']),
                Match(vec!['\n']),
                Match(vec!['\r']),
                Match(vec!['\u{0085}']),
                Match(vec!['\u{00a0}']),
                Match(vec!['\u{1680}']),
                MatchRange('\u{2000}', '\u{200a}'),
                Match(vec!['\u{2028}']),
                Match(vec!['\u{2029}']),
                Match(vec!['\u{202f}']),
                Match(vec!['\u{205f}']),
                Match(vec!['\u{3000}'])
            ]),

            // Just the literal character otherwise
            c => Match(vec![LexTool::special_character_char(c)])
        }
    }

    ///
    /// Finds a subpattern from the index of the '(' that starts it
    ///
    fn get_subpattern<'a>(regex: &'a [char], subpattern_start: usize) -> &'a [char] {
        let start_pos   = subpattern_start+1;
        let mut depth   = 1;
        let mut end_pos = start_pos;
        let regex_len   = regex.len();

        // Subpattern ends at the end of the regex or at the closing ')'
        while end_pos < regex_len && depth > 0 {
            let chr = regex[end_pos];

            match chr {
                '(' => depth += 1,
                ')' => {
                    depth -= 1;
                    if depth == 0 {
                        break;
                    }
                }
                '\\' => end_pos += 1,
                '[' => {
                    // '[)]' isn't a close bracket :-/
                    while end_pos < regex_len && regex[end_pos] != ']' {
                        if regex[end_pos] == '\\' {
                            end_pos += 1;
                        }
                        end_pos += 1;
                    }
                }

                _ => ()
            }

            end_pos += 1;
        }

        &regex[start_pos..end_pos]
    }

    ///
    /// Joins up any sequence of Match<x>, Match<y>
    ///
    fn join_matches(pattern: &mut Vec<Pattern<char>>) {
        let mut index = 1;

        while index < pattern.len() {
            let current = pattern[index].clone();

            if let Match(ref current) = current {
                let previous = pattern[index-1].clone();

                if let Match(ref previous) = previous {
                    // If we have two Matches one after the other, combine them into a phrase
                    // This and some other parts of this code can be improved by concatenating phrases all at once or by building into a new pattern array
                    let mut phrase = previous.clone();
                    phrase.extend(current);

                    pattern[index-1] = Match(phrase);
                    pattern.remove(index);

                    index -= 1;
                }
            }

            index += 1;
        }
    }

    ///
    /// Builds a pattern from a UTF-16 slice
    ///
    pub fn pattern_for_chars(regex: &[char]) -> Pattern<char> {
        // Characters to match exactly as built up so far
        let mut pattern         = vec![];
        let mut or_positions    = vec![];

        // Go through the slice and build up a regex
        let mut pos     = 0;
        let regex_len   = regex.len();

        while pos < regex_len {
            match regex[pos] {
                '\\' => {
                    // Quoted character
                    pos += 1;
                    if pos < regex_len {
                        pattern.push(LexTool::special_character_pattern(regex[pos]))
                    }
                },

                '.' => {
                    // Anything
                    pattern.push(MatchRange('\u{0000}', '\u{10ffff}'))
                },

                '*' => {
                    // Last item repeated
                    let pattern_len = pattern.len();
                    if pattern_len > 0 {
                        if let Some(last) = pattern.last().map(|x| x.clone()) {
                            pattern[pattern_len-1] = RepeatInfinite(0, Box::new(last));
                        }
                    }
                },

                '+' => {
                    // Last item at least once and then repeated
                    let pattern_len = pattern.len();
                    if pattern_len > 0 {
                        if let Some(last) = pattern.last().map(|x| x.clone()) {
                            pattern[pattern_len-1] = RepeatInfinite(1, Box::new(last));
                        }
                    }
                },

                '[' => {
                    // Character ranges
                    let mut ranges = vec![];
                    pos += 1;

                    let mut last_char = None;
                    while pos < regex_len && regex[pos] != ']' {
                        let mut next_char = regex[pos];

                        if next_char == '\\' && pos+1 < regex_len {
                            pos += 1;
                            next_char = LexTool::special_character_char(regex[pos]);
                        }

                        if next_char == '-' && pos < regex_len-1 {
                            pos += 1;
                            let final_char = regex[pos];

                            if let Some(last_char) = last_char {
                                ranges.last_mut().map(|x| *x = (last_char, final_char));
                            }
                        } else {
                            last_char = Some(next_char);
                            ranges.push((next_char, next_char));
                        }

                        pos += 1;
                    }

                    if ranges.len() == 1 {
                        let (first, last) = ranges[0];
                        pattern.push(MatchRange(first, last));
                    } else {
                        pattern.push(MatchAny(ranges.iter().map(|&(first, last)| MatchRange(first, last)).collect()));
                    }
                },

                '|' => {
                    // We'll join the two sides of the 'or' later on
                    or_positions.push(pattern.len());
                },

                '(' => {
                    // Subpattern
                    let subpattern = LexTool::get_subpattern(regex, pos);
                    pattern.push(LexTool::pattern_for_chars(subpattern));

                    pos += subpattern.len()+1;
                },

                c => {
                    // Just match this character
                    pattern.push(Match(vec![c]));
                }
            }

            // Next character
            pos += 1;
        }

        // Join up any subpatterns affected by the 'or' operator
        let mut offset = 0;
        for position_of_or in or_positions {
            if position_of_or > 0 {
                let actual_pos      = position_of_or-offset;
                let (left, right)   = (pattern[actual_pos-1].clone(), pattern[actual_pos].clone());

                pattern.remove(actual_pos);
                pattern[actual_pos-1] = MatchAny(vec![left, right]);

                offset += 1;
            }
        }

        // Join up plain matches
        LexTool::join_matches(&mut pattern);

        // Pattern that we've matched
        if pattern.len() == 0 {
            Epsilon
        } else if pattern.len() == 1 {
            pattern[0].clone()
        } else {
            MatchAll(pattern)
        }
    }
}

impl Tool for LexTool {
    fn invoke_json(&self, input: Value, environment: &Environment) -> Result<Value, Value> {
        // Attempt to parse the input
        let lex_defn = from_value::<LexToolInput>(input);

        // Fetch the tool for defining new tools in this environment
        let define_tool: Result<TypedTool<DefineToolInput, ()>, RetrieveToolError> = environment.get_json_tool(DEFINE_TOOL).map(|tool| TypedTool::from(tool));

        match (lex_defn, define_tool) {
            (Err(erm), _) => {
                // Fail if the input value doesn't deserialize
                Err(json![{
                    "error":        "Parameters incorrect",
                    "description":  erm.description()
                }])
            },

            (_, Err(erm)) => {
                // Fail if there's no define tool
                Err(json![{
                    "error":        "Could not retrieve define-tool",
                    "description":  erm.message()
                }])
            },

            (Ok(lex_defn), Ok(define_tool)) => {
                // Generate a lexer tool for this definition
                let lexer_tool = StringLexingTool::from_lex_tool_input(&lex_defn);

                // Create an environment with just the tool
                let lexer_toolset   = BasicToolSet::from(vec![ ("lex", lexer_tool) ]);
                let lexer_env       = StaticEnvironment::from_toolset(lexer_toolset, &EmptyEnvironment::new());

                // Define it in the environment
                define_tool.invoke(DefineToolInput::new("new", Some(&lex_defn.new_tool_name)), &lexer_env).map(|_| Value::Null)
            }
        }
    }
}

///
/// Tool that reads a string and generates a lexed array of matches
///
pub struct StringLexingTool {
    matcher: TokenMatcher<char, String>
}

impl StringLexingTool {
    ///
    /// Creates a lexer tool from a definition
    ///
    pub fn from_lex_tool_input(lex_defn: &LexToolInput) -> StringLexingTool {
        // Generate a token matcher from the lexer
        let mut token_matcher = TokenMatcher::new();

        for symbol in lex_defn.symbols.iter() {
            token_matcher.add_pattern(LexTool::pattern_for_string(&symbol.match_rule), symbol.symbol_name.clone());
        }

        token_matcher.prepare_to_match();

        // This is what we use in the lexing tool
        StringLexingTool { matcher: token_matcher }
    }
}

impl Tool for StringLexingTool {
    fn invoke_json(&self, input: Value, environment: &Environment) -> Result<Value, Value> {
        if let Value::String(input) = input {
            // Input must be a simple string

            // Start tokenizing it
            let mut tokenizer   = Tokenizer::new(input.read_symbols(), &self.matcher);
            let mut result      = vec![];

            while let Some((range, token)) = tokenizer.next() {
                result.push(json![ {
                    "token":    token,
                    "matched":  input[range.clone()],
                    "start":    range.start,
                    "end":      range.end
                } ]);
            }

            // Final result is an array of tokens
            Ok(Value::Array(result))
        } else {
            Err(json![{
                "error": "Input must be a string"
            }])
        }
    }
}

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

    #[test]
    fn can_create_phrase_match() {
        assert!(LexTool::pattern_for_string("phrase") == Match(vec!['p', 'h', 'r', 'a', 's', 'e']));
    }

    #[test]
    fn can_create_any_pattern() {
        assert!(LexTool::pattern_for_string(".*") == RepeatInfinite(0, Box::new(MatchRange('\u{0000}', '\u{10ffff}'))));
    }

    #[test]
    fn can_create_or_match() {
        assert!(LexTool::pattern_for_string("a|b") == MatchAny(vec![ Match(vec!['a']), Match(vec!['b']) ]));
    }

    #[test]
    fn can_create_nested_or_match() {
        assert!(LexTool::pattern_for_string("a|b|c") == MatchAny(vec![ MatchAny(vec![ Match(vec!['a']), Match(vec!['b']) ]), Match(vec!['c'])] ));
    }

    #[test]
    fn can_create_grouped_or_match() {
        assert!(LexTool::pattern_for_string("(foo)|(bar)") == MatchAny(vec![ Match(vec!['f', 'o', 'o']), Match(vec!['b', 'a', 'r']) ]));
    }

    #[test]
    fn or_is_processed_early() {
        assert!(LexTool::pattern_for_string("foo|bar") == MatchAll(vec![ Match(vec!['f', 'o']), MatchAny(vec![ Match(vec!['o']), Match(vec!['b']) ]), Match(vec!['a', 'r']) ]));
    }

    #[test]
    fn can_create_simple_grouping() {
        assert!(LexTool::pattern_for_string("(phrase)") == Match(vec!['p', 'h', 'r', 'a', 's', 'e']));
    }

    #[test]
    fn can_create_nested_grouping() {
        assert!(LexTool::pattern_for_string("(p(h(r)a)s)e") == Match(vec!['p', 'h', 'r', 'a', 's', 'e']));
    }

    #[test]
    fn can_create_match_one() {
        assert!(LexTool::pattern_for_string("[a]") == MatchRange('a', 'a'));
    }

    #[test]
    fn can_interpret_newline_quote_characters() {
        assert!(LexTool::pattern_for_string("\\n") == Match(vec![ '\n' ]));
    }

    #[test]
    fn can_create_match_range() {
        assert!(LexTool::pattern_for_string("[a-z]") == MatchRange('a', 'z'));
    }

    #[test]
    fn can_create_match_set() {
        assert!(LexTool::pattern_for_string("[acgh]") == MatchAny(vec![ MatchRange('a', 'a'), MatchRange('c', 'c'), MatchRange('g', 'g'), MatchRange('h', 'h') ]));
    }

    #[test]
    fn can_create_match_multi_range() {
        assert!(LexTool::pattern_for_string("[a-zA-Z]") == MatchAny(vec![ MatchRange('a', 'z'), MatchRange('A', 'Z') ]));
    }

    #[test]
    fn can_create_match_set_and_range() {
        assert!(LexTool::pattern_for_string("[aA-Z]") == MatchAny(vec![ MatchRange('a', 'a'), MatchRange('A', 'Z') ]));
    }
}

<|endoftext|>


<commit_before>#![feature(drain)]

#[cfg(test)]
extern crate assert;

#[macro_use]
extern crate log;

extern crate fractal;
extern crate options;
extern crate probability;
extern crate random;
extern crate sql;
extern crate sqlite;
extern crate temperature;
extern crate threed_ice;
extern crate toml;

use std::{error, fmt};

macro_rules! raise(
    ($message:expr) => (return Err(Box::new(::ErrorString($message.to_string()))));
    ($($arg:tt)*) => (return Err(Box::new(::ErrorString(format!($($arg)*)))));
);

macro_rules! ok(
    ($result:expr) => (match $result {
        Ok(result) => result,
        Err(error) => return Err(Box::new(error)),
    });
);

macro_rules! some(
    ($option:expr, $($arg:tt)*) => (match $option {
        Some(value) => value,
        _ => raise!($($arg)*),
    });
);

macro_rules! path(
    ($config:ident, $destination:expr) => ({
        let path = some!($config.get::<String>("path"), "the path to {} is missing", $destination);
        let mut path = ::std::path::PathBuf::from(path);
        if path.is_relative() {
            if let Some(ref root) = $config.get::<::std::path::PathBuf>("root") {
                path = root.join(path);
            }
        }
        if ::std::fs::metadata(&path).is_err() {
            raise!("the file {:?} does not exist", &path);
        }
        path
    });
);

macro_rules! itemize(($blob:item) => ($blob));
macro_rules! rewrite(
    ($header:tt [$($member:tt)*] {}) => (
        rewrite!(output $header [$($member)*]);
    );
    ($header:tt [$($member:tt)*] { pub $name:ident: $kind:ty, $($t:tt)* }) => (
        rewrite!($header [$($member)* pub $name: $kind,] { $($t)* });
    );
    ($header:tt [$($member:tt)*] { $name:ident: $kind:ty, $($t:tt)* }) => (
        rewrite!($header [$($member)* $name: $kind,] { $($t)* });
    );
    (output [$($chunk:tt)*] [$($member:tt)*]) => (
        itemize!(
            $($chunk)* {
                $($member)*
            }
        );
    );
);

macro_rules! rc(
    (
        $(#[$attr:meta])*
        pub struct $outer:ident($inner:ident) $body:tt
    ) => (
        $(#[$attr])*
        pub struct $outer(::std::rc::Rc<$inner>);

        rewrite! {
            [$(#[$attr])* pub struct $inner] [] $body
        }

        impl ::std::ops::Deref for $outer {
            type Target = $inner;

            #[inline]
            fn deref(&self) -> &Self::Target {
                &self.0
            }
        }
    );
    ($outer:ident($inner:ident { $($field:ident: $value:expr),* })) => (
        rc!($outer($inner { $($field: $value,)* }))
    );
    ($outer:ident($inner:ident { $($field:ident: $value:expr,)* })) => (
        $outer(::std::rc::Rc::new($inner { $($field: $value,)* }))
    );
);

macro_rules! time {
    ($name:ident) => (
        impl ::std::cmp::Eq for $name {
        }

        impl ::std::cmp::Ord for $name {
            fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
                if self.time < other.time {
                    ::std::cmp::Ordering::Greater
                } else if self.time > other.time {
                    ::std::cmp::Ordering::Less
                } else {
                    ::std::cmp::Ordering::Equal
                }
            }
        }

        impl ::std::cmp::PartialEq for $name {
            #[inline]
            fn eq(&self, other: &Self) -> bool {
                self.time == other.time
            }
        }

        impl ::std::cmp::PartialOrd for $name {
            #[inline]
            fn partial_cmp(&self, other: &Self) -> Option<::std::cmp::Ordering> {
                Some(self.cmp(other))
            }
        }
    );
}

mod config;
mod platform;
mod profile;
mod schedule;
mod system;
mod traffic;
mod workload;

pub use config::Config;
pub use platform::Platform;
pub use profile::Profile;
pub use system::{Increment, Job, System};

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

pub struct ErrorString(pub String);

pub type Source = random::Default;

impl fmt::Debug for ErrorString {
    #[inline]
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt(formatter)
    }
}

impl fmt::Display for ErrorString {
    #[inline]
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt(formatter)
    }
}

impl error::Error for ErrorString {
    #[inline]
    fn description(&self) -> &str {
        &self.0
    }
}
<commit_msg>Generalize time!<commit_after>#![feature(drain)]

#[cfg(test)]
extern crate assert;

#[macro_use]
extern crate log;

extern crate fractal;
extern crate options;
extern crate probability;
extern crate random;
extern crate sql;
extern crate sqlite;
extern crate temperature;
extern crate threed_ice;
extern crate toml;

use std::{error, fmt};

macro_rules! raise(
    ($message:expr) => (return Err(Box::new(::ErrorString($message.to_string()))));
    ($($arg:tt)*) => (return Err(Box::new(::ErrorString(format!($($arg)*)))));
);

macro_rules! ok(
    ($result:expr) => (match $result {
        Ok(result) => result,
        Err(error) => return Err(Box::new(error)),
    });
);

macro_rules! some(
    ($option:expr, $($arg:tt)*) => (match $option {
        Some(value) => value,
        _ => raise!($($arg)*),
    });
);

macro_rules! path(
    ($config:ident, $destination:expr) => ({
        let path = some!($config.get::<String>("path"), "the path to {} is missing", $destination);
        let mut path = ::std::path::PathBuf::from(path);
        if path.is_relative() {
            if let Some(ref root) = $config.get::<::std::path::PathBuf>("root") {
                path = root.join(path);
            }
        }
        if ::std::fs::metadata(&path).is_err() {
            raise!("the file {:?} does not exist", &path);
        }
        path
    });
);

macro_rules! rewrite(
    ($header:tt [$($member:tt)*] {}) => (
        rewrite!(output $header [$($member)*]);
    );
    ($header:tt [$($member:tt)*] { pub $name:ident: $kind:ty, $($t:tt)* }) => (
        rewrite!($header [$($member)* pub $name: $kind,] { $($t)* });
    );
    ($header:tt [$($member:tt)*] { $name:ident: $kind:ty, $($t:tt)* }) => (
        rewrite!($header [$($member)* $name: $kind,] { $($t)* });
    );
    (output [$($chunk:tt)*] [$($member:tt)*]) => (
        itemize!(
            $($chunk)* {
                $($member)*
            }
        );
    );
);

macro_rules! rc(
    (
        $(#[$attr:meta])*
        pub struct $outer:ident($inner:ident) $body:tt
    ) => (
        $(#[$attr])*
        pub struct $outer(::std::rc::Rc<$inner>);

        rewrite! {
            [$(#[$attr])* pub struct $inner] [] $body
        }

        impl ::std::ops::Deref for $outer {
            type Target = $inner;

            #[inline]
            fn deref(&self) -> &Self::Target {
                &self.0
            }
        }
    );
    ($outer:ident($inner:ident { $($field:ident: $value:expr),* })) => (
        rc!($outer($inner { $($field: $value,)* }))
    );
    ($outer:ident($inner:ident { $($field:ident: $value:expr,)* })) => (
        $outer(::std::rc::Rc::new($inner { $($field: $value,)* }))
    );
);

macro_rules! time {
    ($name:ident($field:tt)) => (itemize! {
        impl ::std::cmp::Eq for $name {
        }

        impl ::std::cmp::Ord for $name {
            fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
                if self.$field < other.$field {
                    ::std::cmp::Ordering::Greater
                } else if self.$field > other.$field {
                    ::std::cmp::Ordering::Less
                } else {
                    ::std::cmp::Ordering::Equal
                }
            }
        }

        impl ::std::cmp::PartialEq for $name {
            #[inline]
            fn eq(&self, other: &Self) -> bool {
                self.$field == other.$field
            }
        }

        impl ::std::cmp::PartialOrd for $name {
            #[inline]
            fn partial_cmp(&self, other: &Self) -> Option<::std::cmp::Ordering> {
                Some(self.cmp(other))
            }
        }
    });
    ($name:ident) => (time! { $name(time) });
}

macro_rules! itemize(($($blob:item)*) => ($($blob)*));

mod config;
mod platform;
mod profile;
mod schedule;
mod system;
mod traffic;
mod workload;

pub use config::Config;
pub use platform::Platform;
pub use profile::Profile;
pub use system::{Increment, Job, System};

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

pub struct ErrorString(pub String);

pub type Source = random::Default;

impl fmt::Debug for ErrorString {
    #[inline]
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt(formatter)
    }
}

impl fmt::Display for ErrorString {
    #[inline]
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt(formatter)
    }
}

impl error::Error for ErrorString {
    #[inline]
    fn description(&self) -> &str {
        &self.0
    }
}
<|endoftext|>