<commit_before>//! The main entry point for the Space API server.
//!
//! Running this code starts a HTTP server instance. The default port is 3000, but you can set your
//! own favorite port by exporting the `PORT` environment variable.

#[macro_use]
extern crate log;
extern crate rustc_serialize;
extern crate iron;
extern crate urlencoded;
extern crate spaceapi;

pub mod datastore;
pub mod sensors;

use std::net::Ipv4Addr;

use rustc_serialize::json::{Json, ToJson};
use iron::prelude::*;
use iron::{status, headers, middleware};
use iron::modifiers::Header;
use urlencoded::UrlEncodedQuery;

pub use spaceapi as api;
use datastore::SafeDataStore;
use spaceapi::optional::Optional;
use spaceapi::SensorTemplate;

/// A Space API server instance.
///
/// You can create a new instance using the ``new`` constructor method by
/// passing it the host, the port, the ``Status`` object and a datastore.
///
/// The ``SpaceapiServer`` includes a web server through
/// [Hyper](http://hyper.rs/hyper/hyper/server/index.html). Simply call the ``serve`` method.
pub struct SpaceapiServer {
    host: Ipv4Addr,
    port: u16,
    status: api::Status,
    datastore: SafeDataStore,
    sensor_specs: Vec<sensors::SensorSpec>,
}

impl SpaceapiServer {

    pub fn new(host: Ipv4Addr, port: u16, status: api::Status, datastore: SafeDataStore) -> SpaceapiServer {
        SpaceapiServer {
            host: host,
            port: port,
            status: status,
            datastore: datastore,
            sensor_specs: vec![],
        }
    }

    /// Update values in the `DataStore`
    fn update_values(&self, map: &urlencoded::QueryMap) -> IronResult<Response> {
        // store data to datastore
        let mut datastore_lock = self.datastore.lock().unwrap();
        println!("{:?}", map);

        for item in map.iter() {
            // TODO: check if key exists and handle errors
            datastore_lock.store(item.0, &item.1[0]);
        }
        let response = Response::with((status::Ok, "updated values"));
        Ok(response)
    }

    /// Start a HTTP server listening on ``self.host:self.port``.
    ///
    /// This call is blocking. It can be interrupted with SIGINT (Ctrl+C).
    pub fn serve(self) {
        let host = self.host;
        let port = self.port;
        println!("Starting HTTP server on http://{}:{}...", host, port);
        Iron::new(self).http((host, port)).unwrap();
    }

    /// Register a new sensor.
    ///
    /// The first argument is a ``api::SensorTemplate`` instance containing all static data.
    /// The second argument specifies how to get the actual sensor value from the datastore.
    /// And the third argument specifies the data type of the value.
    pub fn register_sensor(&mut self, template: Box<api::SensorTemplate>, data_key: String) {
        self.sensor_specs.push(sensors::SensorSpec {
            template: template,
            data_key: data_key,
        });
    }

    fn build_response_json(&self) -> Json {

        // Create a mutable copy of the status struct
        let mut status_copy = self.status.clone();

        // Process registered sensors
        for sensor_spec in &self.sensor_specs {

            sensor_spec.get_sensor_value(&self.datastore).map(|value| {
                if status_copy.sensors.is_absent() {
                    status_copy.sensors = Optional::Value(spaceapi::Sensors {
                        people_now_present: Optional::Absent,
                        temperature: Optional::Absent,
                    });
                }
                sensor_spec.template.to_sensor(&value, &mut status_copy.sensors.as_mut().unwrap());
            });
        }

        status_copy.state.open = status_copy.sensors
                                            .as_ref()
                                            .and_then(|sensors| sensors.people_now_present.as_ref())
                                            .and_then(|people_now_present| {
                                                match people_now_present[0].value {
                                                    0i64 => Optional::Value(false),
                                                    _ => Optional::Value(true),
                                                }
                                            })
                                            .into();

        // Serialize to JSON
        status_copy.to_json()
    }

}

impl middleware::Handler for SpaceapiServer {

    fn handle(&self, req: &mut Request) -> IronResult<Response> {

        match req.get_ref::<UrlEncodedQuery>() {
            Ok(ref hashmap) => return self.update_values(hashmap),
            Err(ref e) => println!("{:?}", e),
        }

        // Get response body
        let body = self.build_response_json().to_string();

        // Create response
        let header_content_type = headers::ContentType("application/json; charset=utf-8".parse().unwrap());
        let header_cache_control = headers::CacheControl(vec![headers::CacheDirective::NoCache]);
        let header_allow_origin = headers::AccessControlAllowOrigin::Any;
        let response = Response::with((status::Ok, body))
                           .set(Header(header_content_type))
                           .set(Header(header_cache_control))
                           .set(Header(header_allow_origin));
        Ok(response)
    }
}

#[cfg(test)]
mod test {
    extern crate spaceapi;
    extern crate rustc_serialize;

    use std::net::Ipv4Addr;
    use std::sync::Mutex;
    use rustc_serialize::json::Json;
    use spaceapi::optional::Optional;
    use super::SpaceapiServer;
    use super::datastore::{DataStore, RedisStore};

    fn get_test_data() -> Json {
        // Create minimal status object
        let status = spaceapi::Status::new(
            "ourspace".to_string(),
            "https://example.com/logo.png".to_string(),
            "https://example.com/".to_string(),
            spaceapi::Location {
                address: Optional::Value("Street 1, Zürich, Switzerland".to_string()),
                lat: 47.123,
                lon: 8.88,
            },
            spaceapi::Contact {
                irc: Optional::Absent,
                twitter: Optional::Absent,
                foursquare: Optional::Absent,
                email: Optional::Value("hi@example.com".to_string()),
            },
            vec![
                "email".to_string(),
                "twitter".to_string(),
            ],
        );

        // Create datastore (TODO: Create dummy store for testing?)
        let datastore = Mutex::new(Box::new(RedisStore::new().unwrap()) as Box<DataStore>);

        // Initialize server
        let server = SpaceapiServer::new(Ipv4Addr::new(127, 0, 0, 1), 3001, status, datastore);

        // Build JSON
        server.build_response_json()
    }

    #[test]
    /// Verify that the result is a JSON object.
    fn verify_json_obj() {
        let json = get_test_data();
        assert!(json.is_object());
    }

    #[test]
    /// Verify that the result has the correct keys.
    fn verify_json_keys() {
        let json = get_test_data();
        let status = json.as_object().unwrap();  // We get back a BTreeMap<String, Json>
        let keys: Vec<String> = status.keys().cloned().collect();  // Collect the keys
        assert_eq!(keys,
                   ["api", "contact", "issue_report_channels", "location", "logo", "space", "state", "url"]);
    }

    #[test]
    /// Verify static data
    fn verify_json_static_data() {
        let json = get_test_data();
        let status = json.as_object().unwrap();  // We get back a BTreeMap<String, Json>

        // Strings
        assert_eq!(status.get("api").unwrap().as_string().unwrap(), "0.13");
        assert_eq!(status.get("space").unwrap().as_string().unwrap(),
                   "ourspace");
        assert_eq!(status.get("url").unwrap().as_string().unwrap(),
                   "https://example.com/");
        assert_eq!(status.get("logo").unwrap().as_string().unwrap(),
                   "https://example.com/logo.png");

        // Channels
        let channels: &Vec<Json> = status.get("issue_report_channels").unwrap().as_array().unwrap();
        let channel_values: Vec<String> = channels.iter()
                                                  .cloned()
                                                  .map(|c| c.as_string().unwrap().to_string())
                                                  .collect();
        assert_eq!(vec!["email", "twitter"], channel_values);
    }

    #[test]
    /// Verify location
    fn verify_json_location() {
        let json = get_test_data();
        let status = json.as_object().unwrap();
        let location = status.get("location").unwrap().as_object().unwrap();

        // Verify data
        let address = location.get("address").unwrap().as_string().unwrap();
        assert_eq!("Street 1, Zürich, Switzerland".to_string(), address);
        assert_eq!(47.123, location.get("lat").unwrap().as_f64().unwrap());
        assert_eq!(8.88, location.get("lon").unwrap().as_f64().unwrap());
    }

    #[test]
    /// Verify contact
    fn verify_json_contact() {
        let json = get_test_data();
        let status = json.as_object().unwrap();
        let contact = status.get("contact").unwrap().as_object().unwrap();

        // Verify data
        let email = contact.get("email").unwrap().as_string().unwrap();
        assert_eq!("hi@example.com".to_string(), email);
    }

    /* TODO: Update
    #[test]
    /// Verify sensor data
    fn verify_json_sensors() {
        let json = get_test_data();
        let status = json.as_object().unwrap();
        let sensors = status.get("sensors").unwrap().as_object().unwrap();
        let temperature_sensor = sensors.get("temperature").unwrap()
                                        .as_array().unwrap()[0]  // List of sensors
                                        .as_object().unwrap();  // Sensor object
        let people_sensor = sensors.get("people_now_present").unwrap()  // List of sensors
                                        .as_array().unwrap()[0]  // Sensor object
                                        .as_object().unwrap();

        // Verify temperature sensor
        let temp_location = temperature_sensor.get("location").unwrap().as_string().unwrap();
        let temp_name = temperature_sensor.get("name").unwrap().as_string().unwrap();
        let temp_unit = temperature_sensor.get("unit").unwrap().as_string().unwrap();
        let temp_value = temperature_sensor.get("value").unwrap().as_f64().unwrap();
        assert_eq!("Basement".to_string(), temp_location);
        assert_eq!("Raspberry CPU".to_string(), temp_name);
        assert_eq!("°C".to_string(), temp_unit);
        assert_eq!(42.5, temp_value);

        // Verify peoplesensor
        let people_location = people_sensor.get("location").unwrap().as_string().unwrap();
        let people_value = people_sensor.get("value").unwrap().as_u64().unwrap();
        assert_eq!("Hackerspace".to_string(), people_location);
        assert_eq!(23, people_value);
    }
    */

}
<commit_msg>Nicer logging<commit_after>//! The main entry point for the Space API server.
//!
//! Running this code starts a HTTP server instance. The default port is 3000, but you can set your
//! own favorite port by exporting the `PORT` environment variable.

#[macro_use]
extern crate log;
extern crate rustc_serialize;
extern crate iron;
extern crate urlencoded;
extern crate spaceapi;

pub mod datastore;
pub mod sensors;

use std::net::Ipv4Addr;

use rustc_serialize::json::{Json, ToJson};
use iron::prelude::*;
use iron::{status, headers, middleware};
use iron::modifiers::Header;
use urlencoded::UrlEncodedQuery;

pub use spaceapi as api;
use datastore::SafeDataStore;
use spaceapi::optional::Optional;
use spaceapi::SensorTemplate;

/// A Space API server instance.
///
/// You can create a new instance using the ``new`` constructor method by
/// passing it the host, the port, the ``Status`` object and a datastore.
///
/// The ``SpaceapiServer`` includes a web server through
/// [Hyper](http://hyper.rs/hyper/hyper/server/index.html). Simply call the ``serve`` method.
pub struct SpaceapiServer {
    host: Ipv4Addr,
    port: u16,
    status: api::Status,
    datastore: SafeDataStore,
    sensor_specs: Vec<sensors::SensorSpec>,
}

impl SpaceapiServer {

    pub fn new(host: Ipv4Addr, port: u16, status: api::Status, datastore: SafeDataStore) -> SpaceapiServer {
        SpaceapiServer {
            host: host,
            port: port,
            status: status,
            datastore: datastore,
            sensor_specs: vec![],
        }
    }

    /// Update values in the `DataStore`
    fn update_values(&self, map: &urlencoded::QueryMap) -> IronResult<Response> {
        // store data to datastore
        let mut datastore_lock = self.datastore.lock().unwrap();
        info!("{:?}", map);

        for item in map.iter() {
            // TODO: check if key exists and handle errors
            datastore_lock.store(item.0, &item.1[0]);
        }
        let response = Response::with((status::Ok, "updated values"));
        Ok(response)
    }

    /// Start a HTTP server listening on ``self.host:self.port``.
    ///
    /// This call is blocking. It can be interrupted with SIGINT (Ctrl+C).
    pub fn serve(self) {
        let host = self.host;
        let port = self.port;
        println!("Starting HTTP server on http://{}:{}...", host, port);
        Iron::new(self).http((host, port)).unwrap();
    }

    /// Register a new sensor.
    ///
    /// The first argument is a ``api::SensorTemplate`` instance containing all static data.
    /// The second argument specifies how to get the actual sensor value from the datastore.
    /// And the third argument specifies the data type of the value.
    pub fn register_sensor(&mut self, template: Box<api::SensorTemplate>, data_key: String) {
        self.sensor_specs.push(sensors::SensorSpec {
            template: template,
            data_key: data_key,
        });
    }

    fn build_response_json(&self) -> Json {

        // Create a mutable copy of the status struct
        let mut status_copy = self.status.clone();

        // Process registered sensors
        for sensor_spec in &self.sensor_specs {

            sensor_spec.get_sensor_value(&self.datastore).map(|value| {
                if status_copy.sensors.is_absent() {
                    status_copy.sensors = Optional::Value(spaceapi::Sensors {
                        people_now_present: Optional::Absent,
                        temperature: Optional::Absent,
                    });
                }
                sensor_spec.template.to_sensor(&value, &mut status_copy.sensors.as_mut().unwrap());
            });
        }

        status_copy.state.open = status_copy.sensors
                                            .as_ref()
                                            .and_then(|sensors| sensors.people_now_present.as_ref())
                                            .and_then(|people_now_present| {
                                                match people_now_present[0].value {
                                                    0i64 => Optional::Value(false),
                                                    _ => Optional::Value(true),
                                                }
                                            })
                                            .into();

        // Serialize to JSON
        status_copy.to_json()
    }

}

impl middleware::Handler for SpaceapiServer {

    fn handle(&self, req: &mut Request) -> IronResult<Response> {

        println!("{} /{} from {}", req.method, req.url.path[0], req.remote_addr);

        match req.get_ref::<UrlEncodedQuery>() {
            Ok(ref hashmap) => return self.update_values(hashmap),
            Err(ref e) => error!("{:?}", e),
        }

        // Get response body
        let body = self.build_response_json().to_string();

        // Create response
        let header_content_type = headers::ContentType("application/json; charset=utf-8".parse().unwrap());
        let header_cache_control = headers::CacheControl(vec![headers::CacheDirective::NoCache]);
        let header_allow_origin = headers::AccessControlAllowOrigin::Any;
        let response = Response::with((status::Ok, body))
                           .set(Header(header_content_type))
                           .set(Header(header_cache_control))
                           .set(Header(header_allow_origin));
        Ok(response)
    }
}

#[cfg(test)]
mod test {
    extern crate spaceapi;
    extern crate rustc_serialize;

    use std::net::Ipv4Addr;
    use std::sync::Mutex;
    use rustc_serialize::json::Json;
    use spaceapi::optional::Optional;
    use super::SpaceapiServer;
    use super::datastore::{DataStore, RedisStore};

    fn get_test_data() -> Json {
        // Create minimal status object
        let status = spaceapi::Status::new(
            "ourspace".to_string(),
            "https://example.com/logo.png".to_string(),
            "https://example.com/".to_string(),
            spaceapi::Location {
                address: Optional::Value("Street 1, Zürich, Switzerland".to_string()),
                lat: 47.123,
                lon: 8.88,
            },
            spaceapi::Contact {
                irc: Optional::Absent,
                twitter: Optional::Absent,
                foursquare: Optional::Absent,
                email: Optional::Value("hi@example.com".to_string()),
            },
            vec![
                "email".to_string(),
                "twitter".to_string(),
            ],
        );

        // Create datastore (TODO: Create dummy store for testing?)
        let datastore = Mutex::new(Box::new(RedisStore::new().unwrap()) as Box<DataStore>);

        // Initialize server
        let server = SpaceapiServer::new(Ipv4Addr::new(127, 0, 0, 1), 3001, status, datastore);

        // Build JSON
        server.build_response_json()
    }

    #[test]
    /// Verify that the result is a JSON object.
    fn verify_json_obj() {
        let json = get_test_data();
        assert!(json.is_object());
    }

    #[test]
    /// Verify that the result has the correct keys.
    fn verify_json_keys() {
        let json = get_test_data();
        let status = json.as_object().unwrap();  // We get back a BTreeMap<String, Json>
        let keys: Vec<String> = status.keys().cloned().collect();  // Collect the keys
        assert_eq!(keys,
                   ["api", "contact", "issue_report_channels", "location", "logo", "space", "state", "url"]);
    }

    #[test]
    /// Verify static data
    fn verify_json_static_data() {
        let json = get_test_data();
        let status = json.as_object().unwrap();  // We get back a BTreeMap<String, Json>

        // Strings
        assert_eq!(status.get("api").unwrap().as_string().unwrap(), "0.13");
        assert_eq!(status.get("space").unwrap().as_string().unwrap(),
                   "ourspace");
        assert_eq!(status.get("url").unwrap().as_string().unwrap(),
                   "https://example.com/");
        assert_eq!(status.get("logo").unwrap().as_string().unwrap(),
                   "https://example.com/logo.png");

        // Channels
        let channels: &Vec<Json> = status.get("issue_report_channels").unwrap().as_array().unwrap();
        let channel_values: Vec<String> = channels.iter()
                                                  .cloned()
                                                  .map(|c| c.as_string().unwrap().to_string())
                                                  .collect();
        assert_eq!(vec!["email", "twitter"], channel_values);
    }

    #[test]
    /// Verify location
    fn verify_json_location() {
        let json = get_test_data();
        let status = json.as_object().unwrap();
        let location = status.get("location").unwrap().as_object().unwrap();

        // Verify data
        let address = location.get("address").unwrap().as_string().unwrap();
        assert_eq!("Street 1, Zürich, Switzerland".to_string(), address);
        assert_eq!(47.123, location.get("lat").unwrap().as_f64().unwrap());
        assert_eq!(8.88, location.get("lon").unwrap().as_f64().unwrap());
    }

    #[test]
    /// Verify contact
    fn verify_json_contact() {
        let json = get_test_data();
        let status = json.as_object().unwrap();
        let contact = status.get("contact").unwrap().as_object().unwrap();

        // Verify data
        let email = contact.get("email").unwrap().as_string().unwrap();
        assert_eq!("hi@example.com".to_string(), email);
    }

    /* TODO: Update
    #[test]
    /// Verify sensor data
    fn verify_json_sensors() {
        let json = get_test_data();
        let status = json.as_object().unwrap();
        let sensors = status.get("sensors").unwrap().as_object().unwrap();
        let temperature_sensor = sensors.get("temperature").unwrap()
                                        .as_array().unwrap()[0]  // List of sensors
                                        .as_object().unwrap();  // Sensor object
        let people_sensor = sensors.get("people_now_present").unwrap()  // List of sensors
                                        .as_array().unwrap()[0]  // Sensor object
                                        .as_object().unwrap();

        // Verify temperature sensor
        let temp_location = temperature_sensor.get("location").unwrap().as_string().unwrap();
        let temp_name = temperature_sensor.get("name").unwrap().as_string().unwrap();
        let temp_unit = temperature_sensor.get("unit").unwrap().as_string().unwrap();
        let temp_value = temperature_sensor.get("value").unwrap().as_f64().unwrap();
        assert_eq!("Basement".to_string(), temp_location);
        assert_eq!("Raspberry CPU".to_string(), temp_name);
        assert_eq!("°C".to_string(), temp_unit);
        assert_eq!(42.5, temp_value);

        // Verify peoplesensor
        let people_location = people_sensor.get("location").unwrap().as_string().unwrap();
        let people_value = people_sensor.get("value").unwrap().as_u64().unwrap();
        assert_eq!("Hackerspace".to_string(), people_location);
        assert_eq!(23, people_value);
    }
    */

}
<|endoftext|>


<commit_before>use std::path::{Path, PathBuf};

use xshell::{cmd, pushd, pushenv, read_file};

use crate::{cargo_files, codegen, project_root, rust_files};

#[test]
fn generate_grammar() {
    codegen::generate_syntax().unwrap()
}

#[test]
fn generate_parser_tests() {
    codegen::generate_parser_tests().unwrap()
}

#[test]
fn generate_assists_tests() {
    codegen::generate_assists_tests().unwrap();
}

/// This clones rustc repo, and so is not worth to keep up-to-date. We update
/// manually by un-ignoring the test from time to time.
#[test]
#[ignore]
fn generate_lint_completions() {
    codegen::generate_lint_completions().unwrap()
}

#[test]
fn check_code_formatting() {
    let _dir = pushd(project_root()).unwrap();
    let _e = pushenv("RUSTUP_TOOLCHAIN", "stable");
    crate::ensure_rustfmt().unwrap();
    let res = cmd!("cargo fmt -- --check").run();
    if res.is_err() {
        let _ = cmd!("cargo fmt").run();
    }
    res.unwrap()
}

#[test]
fn smoke_test_generate_documentation() {
    codegen::docs().unwrap()
}

#[test]
fn check_lsp_extensions_docs() {
    let expected_hash = {
        let lsp_ext_rs =
            read_file(project_root().join("crates/rust-analyzer/src/lsp_ext.rs")).unwrap();
        stable_hash(lsp_ext_rs.as_str())
    };

    let actual_hash = {
        let lsp_extensions_md =
            read_file(project_root().join("docs/dev/lsp-extensions.md")).unwrap();
        let text = lsp_extensions_md
            .lines()
            .find_map(|line| line.strip_prefix("lsp_ext.rs hash:"))
            .unwrap()
            .trim();
        u64::from_str_radix(text, 16).unwrap()
    };

    if actual_hash != expected_hash {
        panic!(
            "
lsp_ext.rs was changed without touching lsp-extensions.md.

Expected hash: {:x}
Actual hash:   {:x}

Please adjust docs/dev/lsp-extensions.md.
",
            expected_hash, actual_hash
        )
    }
}

#[test]
fn rust_files_are_tidy() {
    let mut tidy_docs = TidyDocs::default();
    for path in rust_files() {
        let text = read_file(&path).unwrap();
        check_todo(&path, &text);
        check_dbg(&path, &text);
        check_trailing_ws(&path, &text);
        deny_clippy(&path, &text);
        tidy_docs.visit(&path, &text);
    }
    tidy_docs.finish();
}

#[test]
fn cargo_files_are_tidy() {
    for cargo in cargo_files() {
        let mut section = None;
        for (line_no, text) in read_file(&cargo).unwrap().lines().enumerate() {
            let text = text.trim();
            if text.starts_with('[') {
                if !text.ends_with(']') {
                    panic!(
                        "\nplease don't add comments or trailing whitespace in section lines.\n\
                            {}:{}\n",
                        cargo.display(),
                        line_no + 1
                    )
                }
                section = Some(text);
                continue;
            }
            let text: String = text.split_whitespace().collect();
            if !text.contains("path=") {
                continue;
            }
            match section {
                Some(s) if s.contains("dev-dependencies") => {
                    if text.contains("version") {
                        panic!(
                            "\ncargo internal dev-dependencies should not have a version.\n\
                            {}:{}\n",
                            cargo.display(),
                            line_no + 1
                        );
                    }
                }
                Some(s) if s.contains("dependencies") => {
                    if !text.contains("version") {
                        panic!(
                            "\ncargo internal dependencies should have a version.\n\
                            {}:{}\n",
                            cargo.display(),
                            line_no + 1
                        );
                    }
                }
                _ => {}
            }
        }
    }
}

#[test]
fn check_merge_commits() {
    let stdout = cmd!("git rev-list --merges --invert-grep --author 'bors\\[bot\\]' HEAD~19..")
        .read()
        .unwrap();
    if !stdout.is_empty() {
        panic!(
            "
Merge commits are not allowed in the history.

When updating a pull-request, please rebase your feature branch
on top of master by running `git rebase master`. If rebase fails,
you can re-apply your changes like this:

  # Just look around to see the current state.
  $ git status
  $ git log

  # Abort in-progress rebase and merges, if any.
  $ git rebase --abort
  $ git merge --abort

  # Make the branch point to the latest commit from master,
  # while maintaining your local changes uncommited.
  $ git reset --soft origin/master

  # Commit all changes in a single batch.
  $ git commit -am'My changes'

  # Verify that everything looks alright.
  $ git status
  $ git log

  # Push the changes. We did a rebase, so we need `--force` option.
  # `--force-with-lease` is a more safe (Rusty) version of `--force`.
  $ git push --force-with-lease

  # Verify that both local and remote branch point to the same commit.
  $ git log

And don't fear to mess something up during a rebase -- you can
always restore the previous state using `git ref-log`:

https://github.blog/2015-06-08-how-to-undo-almost-anything-with-git/#redo-after-undo-local
"
        );
    }
}

fn deny_clippy(path: &Path, text: &str) {
    let ignore = &[
        // The documentation in string literals may contain anything for its own purposes
        "ide_completion/src/generated_lint_completions.rs",
    ];
    if ignore.iter().any(|p| path.ends_with(p)) {
        return;
    }

    if text.contains("\u{61}llow(clippy") {
        panic!(
            "\n\nallowing lints is forbidden: {}.
rust-analyzer intentionally doesn't check clippy on CI.
You can allow lint globally via `xtask clippy`.
See https://github.com/rust-lang/rust-clippy/issues/5537 for discussion.

",
            path.display()
        )
    }
}

#[test]
fn check_licenses() {
    let expected = "
0BSD OR MIT OR Apache-2.0
Apache-2.0
Apache-2.0 OR BSL-1.0
Apache-2.0 OR MIT
Apache-2.0/MIT
BSD-3-Clause
CC0-1.0 OR Artistic-2.0
ISC
MIT
MIT / Apache-2.0
MIT OR Apache-2.0
MIT OR Apache-2.0 OR Zlib
MIT OR Zlib OR Apache-2.0
MIT/Apache-2.0
Unlicense OR MIT
Unlicense/MIT
Zlib OR Apache-2.0 OR MIT
"
    .lines()
    .filter(|it| !it.is_empty())
    .collect::<Vec<_>>();

    let meta = cmd!("cargo metadata --format-version 1").read().unwrap();
    let mut licenses = meta
        .split(|c| c == ',' || c == '{' || c == '}')
        .filter(|it| it.contains(r#""license""#))
        .map(|it| it.trim())
        .map(|it| it[r#""license":"#.len()..].trim_matches('"'))
        .collect::<Vec<_>>();
    licenses.sort_unstable();
    licenses.dedup();
    if licenses != expected {
        let mut diff = String::new();

        diff.push_str("New Licenses:\n");
        for &l in licenses.iter() {
            if !expected.contains(&l) {
                diff += &format!("  {}\n", l)
            }
        }

        diff.push_str("\nMissing Licenses:\n");
        for &l in expected.iter() {
            if !licenses.contains(&l) {
                diff += &format!("  {}\n", l)
            }
        }

        panic!("different set of licenses!\n{}", diff);
    }
    assert_eq!(licenses, expected);
}

fn check_todo(path: &Path, text: &str) {
    let need_todo = &[
        // This file itself obviously needs to use todo (<- like this!).
        "tests/tidy.rs",
        // Some of our assists generate `todo!()`.
        "handlers/add_turbo_fish.rs",
        "handlers/generate_function.rs",
        "handlers/fill_match_arms.rs",
        // To support generating `todo!()` in assists, we have `expr_todo()` in
        // `ast::make`.
        "ast/make.rs",
        // The documentation in string literals may contain anything for its own purposes
        "ide_completion/src/generated_lint_completions.rs",
    ];
    if need_todo.iter().any(|p| path.ends_with(p)) {
        return;
    }
    if text.contains("TODO") || text.contains("TOOD") || text.contains("todo!") {
        // Generated by an assist
        if text.contains("${0:todo!()}") {
            return;
        }

        panic!(
            "\nTODO markers or todo! macros should not be committed to the master branch,\n\
             use FIXME instead\n\
             {}\n",
            path.display(),
        )
    }
}

fn check_dbg(path: &Path, text: &str) {
    let need_dbg = &[
        // This file itself obviously needs to use dbg.
        "tests/tidy.rs",
        // Assists to remove `dbg!()`
        "handlers/remove_dbg.rs",
        // We have .dbg postfix
        "ide_completion/src/completions/postfix.rs",
        // The documentation in string literals may contain anything for its own purposes
        "ide_completion/src/lib.rs",
        "ide_completion/src/generated_lint_completions.rs",
        // test for doc test for remove_dbg
        "src/tests/generated.rs",
    ];
    if need_dbg.iter().any(|p| path.ends_with(p)) {
        return;
    }
    if text.contains("dbg!") {
        panic!(
            "\ndbg! macros should not be committed to the master branch,\n\
             {}\n",
            path.display(),
        )
    }
}

fn check_trailing_ws(path: &Path, text: &str) {
    if is_exclude_dir(path, &["test_data"]) {
        return;
    }
    for (line_number, line) in text.lines().enumerate() {
        if line.chars().last().map(char::is_whitespace) == Some(true) {
            panic!("Trailing whitespace in {} at line {}", path.display(), line_number)
        }
    }
}

#[derive(Default)]
struct TidyDocs {
    missing_docs: Vec<String>,
    contains_fixme: Vec<PathBuf>,
}

impl TidyDocs {
    fn visit(&mut self, path: &Path, text: &str) {
        // Tests and diagnostic fixes don't need module level comments.
        if is_exclude_dir(path, &["tests", "test_data", "fixes", "grammar"]) {
            return;
        }

        if is_exclude_file(path) {
            return;
        }

        let first_line = match text.lines().next() {
            Some(it) => it,
            None => return,
        };

        if first_line.starts_with("//!") {
            if first_line.contains("FIXME") {
                self.contains_fixme.push(path.to_path_buf());
            }
        } else {
            if text.contains("// Feature:") || text.contains("// Assist:") {
                return;
            }
            self.missing_docs.push(path.display().to_string());
        }

        fn is_exclude_file(d: &Path) -> bool {
            let file_names = ["tests.rs", "famous_defs_fixture.rs"];

            d.file_name()
                .unwrap_or_default()
                .to_str()
                .map(|f_n| file_names.iter().any(|name| *name == f_n))
                .unwrap_or(false)
        }
    }

    fn finish(self) {
        if !self.missing_docs.is_empty() {
            panic!(
                "\nMissing docs strings\n\n\
                 modules:\n{}\n\n",
                self.missing_docs.join("\n")
            )
        }

        for path in self.contains_fixme {
            panic!("FIXME doc in a fully-documented crate: {}", path.display())
        }
    }
}

fn is_exclude_dir(p: &Path, dirs_to_exclude: &[&str]) -> bool {
    p.strip_prefix(project_root())
        .unwrap()
        .components()
        .rev()
        .skip(1)
        .filter_map(|it| it.as_os_str().to_str())
        .any(|it| dirs_to_exclude.contains(&it))
}

#[allow(deprecated)]
fn stable_hash(text: &str) -> u64 {
    use std::hash::{Hash, Hasher, SipHasher};

    let text = text.replace('\r', "");
    let mut hasher = SipHasher::default();
    text.hash(&mut hasher);
    hasher.finish()
}
<commit_msg>Exclude `crates/ide_db/src/helpers/generated_lints.rs` from `tidy::check_todo`<commit_after>use std::path::{Path, PathBuf};

use xshell::{cmd, pushd, pushenv, read_file};

use crate::{cargo_files, codegen, project_root, rust_files};

#[test]
fn generate_grammar() {
    codegen::generate_syntax().unwrap()
}

#[test]
fn generate_parser_tests() {
    codegen::generate_parser_tests().unwrap()
}

#[test]
fn generate_assists_tests() {
    codegen::generate_assists_tests().unwrap();
}

/// This clones rustc repo, and so is not worth to keep up-to-date. We update
/// manually by un-ignoring the test from time to time.
#[test]
#[ignore]
fn generate_lint_completions() {
    codegen::generate_lint_completions().unwrap()
}

#[test]
fn check_code_formatting() {
    let _dir = pushd(project_root()).unwrap();
    let _e = pushenv("RUSTUP_TOOLCHAIN", "stable");
    crate::ensure_rustfmt().unwrap();
    let res = cmd!("cargo fmt -- --check").run();
    if res.is_err() {
        let _ = cmd!("cargo fmt").run();
    }
    res.unwrap()
}

#[test]
fn smoke_test_generate_documentation() {
    codegen::docs().unwrap()
}

#[test]
fn check_lsp_extensions_docs() {
    let expected_hash = {
        let lsp_ext_rs =
            read_file(project_root().join("crates/rust-analyzer/src/lsp_ext.rs")).unwrap();
        stable_hash(lsp_ext_rs.as_str())
    };

    let actual_hash = {
        let lsp_extensions_md =
            read_file(project_root().join("docs/dev/lsp-extensions.md")).unwrap();
        let text = lsp_extensions_md
            .lines()
            .find_map(|line| line.strip_prefix("lsp_ext.rs hash:"))
            .unwrap()
            .trim();
        u64::from_str_radix(text, 16).unwrap()
    };

    if actual_hash != expected_hash {
        panic!(
            "
lsp_ext.rs was changed without touching lsp-extensions.md.

Expected hash: {:x}
Actual hash:   {:x}

Please adjust docs/dev/lsp-extensions.md.
",
            expected_hash, actual_hash
        )
    }
}

#[test]
fn rust_files_are_tidy() {
    let mut tidy_docs = TidyDocs::default();
    for path in rust_files() {
        let text = read_file(&path).unwrap();
        check_todo(&path, &text);
        check_dbg(&path, &text);
        check_trailing_ws(&path, &text);
        deny_clippy(&path, &text);
        tidy_docs.visit(&path, &text);
    }
    tidy_docs.finish();
}

#[test]
fn cargo_files_are_tidy() {
    for cargo in cargo_files() {
        let mut section = None;
        for (line_no, text) in read_file(&cargo).unwrap().lines().enumerate() {
            let text = text.trim();
            if text.starts_with('[') {
                if !text.ends_with(']') {
                    panic!(
                        "\nplease don't add comments or trailing whitespace in section lines.\n\
                            {}:{}\n",
                        cargo.display(),
                        line_no + 1
                    )
                }
                section = Some(text);
                continue;
            }
            let text: String = text.split_whitespace().collect();
            if !text.contains("path=") {
                continue;
            }
            match section {
                Some(s) if s.contains("dev-dependencies") => {
                    if text.contains("version") {
                        panic!(
                            "\ncargo internal dev-dependencies should not have a version.\n\
                            {}:{}\n",
                            cargo.display(),
                            line_no + 1
                        );
                    }
                }
                Some(s) if s.contains("dependencies") => {
                    if !text.contains("version") {
                        panic!(
                            "\ncargo internal dependencies should have a version.\n\
                            {}:{}\n",
                            cargo.display(),
                            line_no + 1
                        );
                    }
                }
                _ => {}
            }
        }
    }
}

#[test]
fn check_merge_commits() {
    let stdout = cmd!("git rev-list --merges --invert-grep --author 'bors\\[bot\\]' HEAD~19..")
        .read()
        .unwrap();
    if !stdout.is_empty() {
        panic!(
            "
Merge commits are not allowed in the history.

When updating a pull-request, please rebase your feature branch
on top of master by running `git rebase master`. If rebase fails,
you can re-apply your changes like this:

  # Just look around to see the current state.
  $ git status
  $ git log

  # Abort in-progress rebase and merges, if any.
  $ git rebase --abort
  $ git merge --abort

  # Make the branch point to the latest commit from master,
  # while maintaining your local changes uncommited.
  $ git reset --soft origin/master

  # Commit all changes in a single batch.
  $ git commit -am'My changes'

  # Verify that everything looks alright.
  $ git status
  $ git log

  # Push the changes. We did a rebase, so we need `--force` option.
  # `--force-with-lease` is a more safe (Rusty) version of `--force`.
  $ git push --force-with-lease

  # Verify that both local and remote branch point to the same commit.
  $ git log

And don't fear to mess something up during a rebase -- you can
always restore the previous state using `git ref-log`:

https://github.blog/2015-06-08-how-to-undo-almost-anything-with-git/#redo-after-undo-local
"
        );
    }
}

fn deny_clippy(path: &Path, text: &str) {
    let ignore = &[
        // The documentation in string literals may contain anything for its own purposes
        "ide_db/src/helpers/generated_lints.rs",
        // The tests test clippy lint hovers
        "ide/src/hover.rs",
    ];
    if ignore.iter().any(|p| path.ends_with(p)) {
        return;
    }

    if text.contains("\u{61}llow(clippy") {
        panic!(
            "\n\nallowing lints is forbidden: {}.
rust-analyzer intentionally doesn't check clippy on CI.
You can allow lint globally via `xtask clippy`.
See https://github.com/rust-lang/rust-clippy/issues/5537 for discussion.

",
            path.display()
        )
    }
}

#[test]
fn check_licenses() {
    let expected = "
0BSD OR MIT OR Apache-2.0
Apache-2.0
Apache-2.0 OR BSL-1.0
Apache-2.0 OR MIT
Apache-2.0/MIT
BSD-3-Clause
CC0-1.0 OR Artistic-2.0
ISC
MIT
MIT / Apache-2.0
MIT OR Apache-2.0
MIT OR Apache-2.0 OR Zlib
MIT OR Zlib OR Apache-2.0
MIT/Apache-2.0
Unlicense OR MIT
Unlicense/MIT
Zlib OR Apache-2.0 OR MIT
"
    .lines()
    .filter(|it| !it.is_empty())
    .collect::<Vec<_>>();

    let meta = cmd!("cargo metadata --format-version 1").read().unwrap();
    let mut licenses = meta
        .split(|c| c == ',' || c == '{' || c == '}')
        .filter(|it| it.contains(r#""license""#))
        .map(|it| it.trim())
        .map(|it| it[r#""license":"#.len()..].trim_matches('"'))
        .collect::<Vec<_>>();
    licenses.sort_unstable();
    licenses.dedup();
    if licenses != expected {
        let mut diff = String::new();

        diff.push_str("New Licenses:\n");
        for &l in licenses.iter() {
            if !expected.contains(&l) {
                diff += &format!("  {}\n", l)
            }
        }

        diff.push_str("\nMissing Licenses:\n");
        for &l in expected.iter() {
            if !licenses.contains(&l) {
                diff += &format!("  {}\n", l)
            }
        }

        panic!("different set of licenses!\n{}", diff);
    }
    assert_eq!(licenses, expected);
}

fn check_todo(path: &Path, text: &str) {
    let need_todo = &[
        // This file itself obviously needs to use todo (<- like this!).
        "tests/tidy.rs",
        // Some of our assists generate `todo!()`.
        "handlers/add_turbo_fish.rs",
        "handlers/generate_function.rs",
        "handlers/fill_match_arms.rs",
        // To support generating `todo!()` in assists, we have `expr_todo()` in
        // `ast::make`.
        "ast/make.rs",
        // The documentation in string literals may contain anything for its own purposes
        "ide_db/src/helpers/generated_lints.rs",
    ];
    if need_todo.iter().any(|p| path.ends_with(p)) {
        return;
    }
    if text.contains("TODO") || text.contains("TOOD") || text.contains("todo!") {
        // Generated by an assist
        if text.contains("${0:todo!()}") {
            return;
        }

        panic!(
            "\nTODO markers or todo! macros should not be committed to the master branch,\n\
             use FIXME instead\n\
             {}\n",
            path.display(),
        )
    }
}

fn check_dbg(path: &Path, text: &str) {
    let need_dbg = &[
        // This file itself obviously needs to use dbg.
        "tests/tidy.rs",
        // Assists to remove `dbg!()`
        "handlers/remove_dbg.rs",
        // We have .dbg postfix
        "ide_completion/src/completions/postfix.rs",
        // The documentation in string literals may contain anything for its own purposes
        "ide_completion/src/lib.rs",
        "ide_db/src/helpers/generated_lints.rs",
        // test for doc test for remove_dbg
        "src/tests/generated.rs",
    ];
    if need_dbg.iter().any(|p| path.ends_with(p)) {
        return;
    }
    if text.contains("dbg!") {
        panic!(
            "\ndbg! macros should not be committed to the master branch,\n\
             {}\n",
            path.display(),
        )
    }
}

fn check_trailing_ws(path: &Path, text: &str) {
    if is_exclude_dir(path, &["test_data"]) {
        return;
    }
    for (line_number, line) in text.lines().enumerate() {
        if line.chars().last().map(char::is_whitespace) == Some(true) {
            panic!("Trailing whitespace in {} at line {}", path.display(), line_number)
        }
    }
}

#[derive(Default)]
struct TidyDocs {
    missing_docs: Vec<String>,
    contains_fixme: Vec<PathBuf>,
}

impl TidyDocs {
    fn visit(&mut self, path: &Path, text: &str) {
        // Tests and diagnostic fixes don't need module level comments.
        if is_exclude_dir(path, &["tests", "test_data", "fixes", "grammar"]) {
            return;
        }

        if is_exclude_file(path) {
            return;
        }

        let first_line = match text.lines().next() {
            Some(it) => it,
            None => return,
        };

        if first_line.starts_with("//!") {
            if first_line.contains("FIXME") {
                self.contains_fixme.push(path.to_path_buf());
            }
        } else {
            if text.contains("// Feature:") || text.contains("// Assist:") {
                return;
            }
            self.missing_docs.push(path.display().to_string());
        }

        fn is_exclude_file(d: &Path) -> bool {
            let file_names = ["tests.rs", "famous_defs_fixture.rs"];

            d.file_name()
                .unwrap_or_default()
                .to_str()
                .map(|f_n| file_names.iter().any(|name| *name == f_n))
                .unwrap_or(false)
        }
    }

    fn finish(self) {
        if !self.missing_docs.is_empty() {
            panic!(
                "\nMissing docs strings\n\n\
                 modules:\n{}\n\n",
                self.missing_docs.join("\n")
            )
        }

        for path in self.contains_fixme {
            panic!("FIXME doc in a fully-documented crate: {}", path.display())
        }
    }
}

fn is_exclude_dir(p: &Path, dirs_to_exclude: &[&str]) -> bool {
    p.strip_prefix(project_root())
        .unwrap()
        .components()
        .rev()
        .skip(1)
        .filter_map(|it| it.as_os_str().to_str())
        .any(|it| dirs_to_exclude.contains(&it))
}

#[allow(deprecated)]
fn stable_hash(text: &str) -> u64 {
    use std::hash::{Hash, Hasher, SipHasher};

    let text = text.replace('\r', "");
    let mut hasher = SipHasher::default();
    text.hash(&mut hasher);
    hasher.finish()
}
<|endoftext|>


<commit_before>use std::fmt::{Write, Error as FmtError};

/// Simple pretty printer hand tailored for the output generated by the `quote`
/// crate for schema inference.
///
/// # Rules
///
/// 1. Seeing `{` increases indentation level
/// 2. Seeing `}` decreases indentation level
/// 3. Insert newline after `{`, `}`, `,`, and `;`
/// 4. Don't put spaces:
///   - between ident and `!`,
///   - between path segments and `::`
///   - after `(`, '<' and before `)`, `>`
///   - before `,`
pub fn format_schema(schema: &str) -> Result<String, FmtError> {
    let mut out = String::with_capacity(schema.len());
    let mut indent = String::new();
    let mut skip_space = false;
    let mut last_char = ' ';

    for c in schema.chars() {
        // The `quote!` macro inserts whitespaces at some strange location,
        // let's remove them!
        match c {
            '!' | '(' | ';' | ',' | '<' | ')' | '>' if last_char.is_whitespace() => {
                out.pop();
            },
            ':' if last_char.is_whitespace() => {
                // Unless we are at the beginning of a fully qualified path,
                // remove the whitespace.
                let char_before_whitespace = {
                    let mut chars = out.chars();
                    chars.next_back();
                    chars.next_back()
                };

                if char_before_whitespace != Some('>') {
                    out.pop();
                }
            },
            _=> { }
        }

        if skip_space && c.is_whitespace() && last_char != '>' {
            skip_space = false;
            continue;
        }

        last_char = c;
        skip_space = false;

        // At this point, there is an empty line before `{`. We need to remove
        // the already inserted indent, because the new indent is smaller than
        // the old one.
        if c == '}' {
            while let Some(c) = out.pop() {
                if c == '\n' {
                    break;
                }
            }

            indent.pop();
            write!(out, "\n{}", indent)?;
        }

        write!(out, "{}", c)?;

        // We need to insert newlines in some places and adjust the indent.
        // Also, we need to remember if we could skip the next whitespace.
        match c {
            ';' | ',' | '}' => {
                skip_space = true;
                write!(out, "\n{}", indent)?;
            },
            '{' => {
                skip_space = true;
                indent += "\t";
                write!(out, "\n{}", indent)?;
            },
            ':' | '(' => skip_space = true,
            _=>{}
        }
    }

    Ok(out.replace("\t", "    "))
}

#[cfg(test)]
mod tests {
    use super::format_schema;
    use std::io::Cursor;

    fn run_test(input: &str, expected: &str){
        let out = Vec::<u8>::new();
        let mut c = Cursor::new(out);
        format_schema(input, &mut c).unwrap();
        let actual = String::from_utf8(c.into_inner()).unwrap();
        assert_eq!(expected, actual);
    }

    #[test]
    fn test_remove_whitespace_colon() {
        run_test(":: diesel :: types :: Text", "::diesel::types::Text");
    }

    #[test]
    fn test_format_nullable() {
        run_test("Nullable < :: diesel :: types :: Text >",
            "Nullable<::diesel::types::Text>");
    }

    #[test]
    fn test_newline_after_comma() {
        run_test(",", ",\n");
    }

    #[test]
    fn test_increase_indent() {
        run_test("{","{\n    ");
    }

    #[test]
    fn test_decrease_indent() {
        run_test("{abc,}","{\n    abc,\n}\n");
    }

    #[test]
    fn test_format_arrow() {
        run_test("created_at -> :: diesel :: types :: Timestamp",
            "created_at -> ::diesel::types::Timestamp");
    }

    #[test]
    fn test_format_full_line() {
        run_test("created_at -> :: diesel :: types :: Timestamp ,",
            "created_at -> ::diesel::types::Timestamp,\n");
    }

    #[test]
    fn test_format_include_line() {
        run_test("pub use self :: infer_locks :: * ;",
            "pub use self::infer_locks::*;\n");
    }

    #[test]
    fn test_format_generated_mod() {
        run_test("mod infer_users { table ! { users ( id ) { id -> :: diesel :: types :: Int4 , username -> :: diesel :: types :: Varchar , password -> :: diesel :: types :: Varchar , } } } pub use self :: infer_users :: * ;",
r"mod infer_users {
    table! {
        users(id) {
            id -> ::diesel::types::Int4,
            username -> ::diesel::types::Varchar,
            password -> ::diesel::types::Varchar,
        }
    }
}
pub use self::infer_users::*;
");
    }
}
<commit_msg>Fix pull-request<commit_after>use std::fmt::{Write, Error as FmtError};

/// Simple pretty printer hand tailored for the output generated by the `quote`
/// crate for schema inference.
///
/// # Rules
///
/// 1. Seeing `{` increases indentation level
/// 2. Seeing `}` decreases indentation level
/// 3. Insert newline after `{`, `}`, `,`, and `;`
/// 4. Don't put spaces:
///   - between ident and `!`,
///   - between path segments and `::`
///   - after `(`, '<' and before `)`, `>`
///   - before `,`
pub fn format_schema(schema: &str) -> Result<String, FmtError> {
    let mut out = String::with_capacity(schema.len());
    let mut indent = String::new();
    let mut skip_space = false;
    let mut last_char = ' ';

    for c in schema.chars() {
        // The `quote!` macro inserts whitespaces at some strange location,
        // let's remove them!
        match c {
            '!' | '(' | ';' | ',' | '<' | ')' | '>' if last_char.is_whitespace() => {
                out.pop();
            },
            ':' if last_char.is_whitespace() => {
                // Unless we are at the beginning of a fully qualified path,
                // remove the whitespace.
                let char_before_whitespace = {
                    let mut chars = out.chars();
                    chars.next_back();
                    chars.next_back()
                };

                if char_before_whitespace != Some('>') {
                    out.pop();
                }
            },
            _=> { }
        }

        if skip_space && c.is_whitespace() && last_char != '>' {
            skip_space = false;
            continue;
        }

        last_char = c;
        skip_space = false;

        // At this point, there is an empty line before `}`. We need to remove
        // the already inserted indent, because the new indent is smaller than
        // the old one.
        if c == '}' {
            while let Some(c) = out.pop() {
                if c == '\n' {
                    break;
                }
            }

            indent.pop();
            write!(out, "\n{}", indent)?;
        }

        write!(out, "{}", c)?;

        // We need to insert newlines in some places and adjust the indent.
        // Also, we need to remember if we could skip the next whitespace.
        match c {
            ';' | ',' | '}' => {
                skip_space = true;
                write!(out, "\n{}", indent)?;
            },
            '{' => {
                skip_space = true;
                indent += "\t";
                write!(out, "\n{}", indent)?;
            },
            ':' | '(' => skip_space = true,
            _=>{}
        }
    }

    Ok(out.replace("\t", "    "))
}

#[cfg(test)]
mod tests {
    use super::format_schema;
    use std::io::Cursor;

    fn run_test(input: &str, expected: &str){
        let actual = format_schema(input).unwrap();
        assert_eq!(expected, actual);
    }

    #[test]
    fn test_remove_whitespace_colon() {
        run_test(":: diesel :: types :: Text", "::diesel::types::Text");
    }

    #[test]
    fn test_format_nullable() {
        run_test("Nullable < :: diesel :: types :: Text >",
            "Nullable<::diesel::types::Text>");
    }

    #[test]
    fn test_newline_after_comma() {
        run_test(",", ",\n");
    }

    #[test]
    fn test_increase_indent() {
        run_test("{","{\n    ");
    }

    #[test]
    fn test_decrease_indent() {
        run_test("{abc,}","{\n    abc,\n}\n");
    }

    #[test]
    fn test_format_arrow() {
        run_test("created_at -> :: diesel :: types :: Timestamp",
            "created_at -> ::diesel::types::Timestamp");
    }

    #[test]
    fn test_format_full_line() {
        run_test("created_at -> :: diesel :: types :: Timestamp ,",
            "created_at -> ::diesel::types::Timestamp,\n");
    }

    #[test]
    fn test_format_include_line() {
        run_test("pub use self :: infer_locks :: * ;",
            "pub use self::infer_locks::*;\n");
    }

    #[test]
    fn test_format_generated_mod() {
        run_test("mod infer_users { table ! { users ( id ) { id -> :: diesel :: types :: Int4 , username -> :: diesel :: types :: Varchar , password -> :: diesel :: types :: Varchar , } } } pub use self :: infer_users :: * ;",
r"mod infer_users {
    table! {
        users(id) {
            id -> ::diesel::types::Int4,
            username -> ::diesel::types::Varchar,
            password -> ::diesel::types::Varchar,
        }
    }
}
pub use self::infer_users::*;
");
    }
}
<|endoftext|>


<commit_before>// Copyright 2016 Alexander Reece
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::collections::BTreeMap;

use super::FormatRustCode;
use super::{format_to_map, format_to_slice};
use {Assertion, Constant, Class, ClassField, ClassMethod, ClassMethodField, Domain, Version};

impl<'a> FormatRustCode for BTreeMap<String, Class<'a>> {
    fn format_rust(&self) -> String {
        format!("{}", format_to_map(self.iter()))
    }
}

impl<'a> FormatRustCode for BTreeMap<String, Domain<'a>> {
    fn format_rust(&self) -> String {
        let iter = self.iter().map(|(k, v)| (k.replace(" ", "-"), v));
        format!("{}", format_to_map(iter))
    }
}

impl<'a> FormatRustCode for Version {
    fn format_rust(&self) -> String {
        format!(
            "Version {{ minor: {}, revision: {} }}",
            self.minor(),
            self.revision(),
        )
    }
}

impl<'a> FormatRustCode for Class<'a> {
    fn format_rust(&self) -> String {
        format!(
            "Class {{\nname: {},\nfields: {},\nindex: {},\nmethods: {}\n}}",
            self.name().format_rust(),
            self.fields().format_rust(),
            self.index(),
            self.methods().format_rust(),
        )
    }
}

impl<'a> FormatRustCode for ClassField<'a> {
    fn format_rust(&self) -> String {
        format!(
            "ClassField {{\nname: {},\ndomain: {}\n}}",
            self.name().replace(" ", "-").format_rust(),
            self.domain().replace(" ", "-").format_rust(),
        )
    }
}

impl<'a> FormatRustCode for Domain<'a> {
    fn format_rust(&self) -> String {
        self.mapping().format_rust()
    }
}

impl<'a> FormatRustCode for Constant<'a> {
    fn format_rust(&self) -> String {
        format!(
            "Constant {{\nname: {},\nvalue: {},\nclass: {}\n}}",
            self.name().replace(" ", "-").format_rust(),
            self.value(),
            self.class().map(|c| c.replace(" ", "-")).format_rust()
        )
    }
}

impl<'a> FormatRustCode for ClassMethod<'a> {
    fn format_rust(&self) -> String {
        let chassis = self.chassis();
        let chassis_client = chassis.get("client");
        let chassis_server = chassis.get("server");
        format!(
            "ClassMethod {{\nname: {},\nindex: {},\nchassis_client: {},\nchassis_server: {},\nresponse: {},\nis_synchronous: {},\nfields: {},\n}}",
            self.name().format_rust(),
            self.index(),
            chassis_client.format_rust(),
            chassis_server.format_rust(),
            self.response().format_rust(),
            self.is_synchronous().format_rust(),
            self.fields().format_rust(),
        )
    }
}

impl<'a> FormatRustCode for ClassMethodField<'a> {
    fn format_rust(&self) -> String {
        format!(
            "ClassMethodField {{\nname: {},\ndomain: {},\nassertions: {},\nis_reserved: {}\n}}",
            self.name().replace(" ", "-").format_rust(),
            self.domain().replace(" ", "-").format_rust(),
            self.assertions().format_rust(),
            self.is_reserved().format_rust()
        )
    }
}

impl FormatRustCode for Assertion {
    fn format_rust(&self) -> String {
        match *self {
            Assertion::Null => "ClassMethodFieldAssertion::Null".to_string(),
            Assertion::NotNull => "ClassMethodFieldAssertion::NotNull".to_string(),
            Assertion::ChannelMax => "ClassMethodFieldAssertion::ChannelMax".to_string(),
            Assertion::NotZero => "ClassMethodFieldAssertion::NotZero".to_string(),
            Assertion::Enum(ref values) => {
                format!("ClassMethodFieldAssertion::Enum({})", format_to_slice(values.iter()))
            },
            Assertion::Length(ref length) => format!("ClassMethodFieldAssertion::Length({})", length),
            Assertion::Regexp(ref pattern) => {
                format!("ClassMethodFieldAssertion::Regexp({})", pattern.format_rust())
            },
            Assertion::Syntax(ref syntax) => {
                format!("ClassMethodFieldAssertion::Syntax({})", syntax.format_rust())
            },
        }
    }
}<commit_msg>[specgen] Remove redundant references<commit_after>// Copyright 2016 Alexander Reece
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::collections::BTreeMap;

use super::FormatRustCode;
use super::{format_to_map, format_to_slice};
use {Assertion, Constant, Class, ClassField, ClassMethod, ClassMethodField, Domain, Version};

impl<'a> FormatRustCode for BTreeMap<String, Class<'a>> {
    fn format_rust(&self) -> String {
        format_to_map(self.iter())
    }
}

impl<'a> FormatRustCode for BTreeMap<String, Domain<'a>> {
    fn format_rust(&self) -> String {
        let iter = self.iter().map(|(k, v)| (k.replace(" ", "-"), v));
        format_to_map(iter)
    }
}

impl<'a> FormatRustCode for Version {
    fn format_rust(&self) -> String {
        format!(
            "Version {{ minor: {}, revision: {} }}",
            self.minor(),
            self.revision(),
        )
    }
}

impl<'a> FormatRustCode for Class<'a> {
    fn format_rust(&self) -> String {
        format!(
            "Class {{\nname: {},\nfields: {},\nindex: {},\nmethods: {}\n}}",
            self.name().format_rust(),
            self.fields().format_rust(),
            self.index(),
            self.methods().format_rust(),
        )
    }
}

impl<'a> FormatRustCode for ClassField<'a> {
    fn format_rust(&self) -> String {
        format!(
            "ClassField {{\nname: {},\ndomain: {}\n}}",
            self.name().replace(" ", "-").format_rust(),
            self.domain().replace(" ", "-").format_rust(),
        )
    }
}

impl<'a> FormatRustCode for Domain<'a> {
    fn format_rust(&self) -> String {
        self.mapping().format_rust()
    }
}

impl<'a> FormatRustCode for Constant<'a> {
    fn format_rust(&self) -> String {
        format!(
            "Constant {{\nname: {},\nvalue: {},\nclass: {}\n}}",
            self.name().replace(" ", "-").format_rust(),
            self.value(),
            self.class().map(|c| c.replace(" ", "-")).format_rust()
        )
    }
}

impl<'a> FormatRustCode for ClassMethod<'a> {
    fn format_rust(&self) -> String {
        let chassis = self.chassis();
        let chassis_client = chassis.get("client");
        let chassis_server = chassis.get("server");
        format!(
            "ClassMethod {{\nname: {},\nindex: {},\nchassis_client: {},\nchassis_server: {},\nresponse: {},\nis_synchronous: {},\nfields: {},\n}}",
            self.name().format_rust(),
            self.index(),
            chassis_client.format_rust(),
            chassis_server.format_rust(),
            self.response().format_rust(),
            self.is_synchronous().format_rust(),
            self.fields().format_rust(),
        )
    }
}

impl<'a> FormatRustCode for ClassMethodField<'a> {
    fn format_rust(&self) -> String {
        format!(
            "ClassMethodField {{\nname: {},\ndomain: {},\nassertions: {},\nis_reserved: {}\n}}",
            self.name().replace(" ", "-").format_rust(),
            self.domain().replace(" ", "-").format_rust(),
            self.assertions().format_rust(),
            self.is_reserved().format_rust()
        )
    }
}

impl FormatRustCode for Assertion {
    fn format_rust(&self) -> String {
        match *self {
            Assertion::Null => "ClassMethodFieldAssertion::Null".to_string(),
            Assertion::NotNull => "ClassMethodFieldAssertion::NotNull".to_string(),
            Assertion::ChannelMax => "ClassMethodFieldAssertion::ChannelMax".to_string(),
            Assertion::NotZero => "ClassMethodFieldAssertion::NotZero".to_string(),
            Assertion::Enum(ref values) => {
                format!("ClassMethodFieldAssertion::Enum({})", format_to_slice(values.iter()))
            },
            Assertion::Length(ref length) => format!("ClassMethodFieldAssertion::Length({})", length),
            Assertion::Regexp(ref pattern) => {
                format!("ClassMethodFieldAssertion::Regexp({})", pattern.format_rust())
            },
            Assertion::Syntax(ref syntax) => {
                format!("ClassMethodFieldAssertion::Syntax({})", syntax.format_rust())
            },
        }
    }
}<|endoftext|>


<commit_before>// Implements http://rosettacode.org/wiki/Assertions
// not_tested

fn main() {
	let my_number = 42i;
	assert!(my_number == 42i);
	assert_eq!(my_number, 42i);
}
<commit_msg>assertions: inference<commit_after>// Implements http://rosettacode.org/wiki/Assertions
// not_tested

fn main() {
	let my_number = 42i;
	assert!(my_number == 42);
	assert_eq!(my_number, 42);
}
<|endoftext|>


<commit_before>// Copyright 2014 Johannes Köster.
// Licensed under the MIT license (http://opensource.org/licenses/MIT)
// This file may not be copied, modified, or distributed
// except according to those terms.

use std::slice;
use std::ffi;

use htslib;

pub type SampleSubset = Vec<i32>;

/// A BCF header.
pub struct Header {
    pub inner: *mut htslib::vcf::bcf_hdr_t,
    pub subset: Option<SampleSubset>,
}

impl Header {
    /// Create a new header.
    pub fn new() -> Self {
        Header {
            inner: unsafe { htslib::vcf::bcf_hdr_init(ffi::CString::new(&b"w"[..]).unwrap().as_ptr()) },
            subset: None,
        }
    }

    pub fn with_template(header: &HeaderView) -> Self {
        Header { inner: unsafe { htslib::vcf::bcf_hdr_dup(header.inner) }, subset: None }
    }

    pub fn subset_template(header: &HeaderView, samples: &[&[u8]]) -> Result<Self, ()> {
        let mut imap = vec![0; samples.len()];
        let names: Vec<_> = samples.iter().map(|&s| ffi::CString::new(s).unwrap()).collect();
        let name_pointers: Vec<_> = names.iter().map(|s| s.as_ptr() as *mut i8).collect();
        let inner = unsafe {
            htslib::vcf::bcf_hdr_subset(header.inner, samples.len() as i32, name_pointers.as_ptr(), imap.as_mut_ptr() as *mut i32)
        };
        if inner.is_null() {
            Err(())
        }
        else {
            Ok(Header { inner: inner, subset: Some(imap) })
        }
    }

    pub fn push_sample(&mut self, sample: &[u8]) -> &mut Self {
        unsafe { htslib::vcf::bcf_hdr_add_sample(self.inner, ffi::CString::new(sample).unwrap().as_ptr()) };
        self
    }

    /// Add a record to the header.
    pub fn push_record(&mut self, record: &[u8]) -> &mut Self {
        unsafe { htslib::vcf::bcf_hdr_append(self.inner, ffi::CString::new(record).unwrap().as_ptr()) };
        self
    }

    pub fn remove_info(&mut self, tag: &[u8]) -> &mut Self {
        unsafe {
            htslib::vcf::bcf_hdr_remove(self.inner, htslib::vcf::BCF_HL_INFO, tag.as_ptr() as *const i8);
        }
        self
    }

    pub fn remove_format(&mut self, tag: &[u8]) -> &mut Self {
        unsafe {
            htslib::vcf::bcf_hdr_remove(self.inner, htslib::vcf::BCF_HL_FMT, tag.as_ptr() as *const i8);
        }
        self
    }
}

impl Drop for Header {
    fn drop(&mut self) {
        unsafe { htslib::vcf::bcf_hdr_destroy(self.inner) };
    }
}

pub struct HeaderView {
    pub inner: *mut htslib::vcf::bcf_hdr_t,
}

impl HeaderView {
    pub fn new(inner: *mut htslib::vcf::bcf_hdr_t) -> Self {
        HeaderView { inner: inner }
    }

    #[inline]
    fn inner(&self) -> htslib::vcf::bcf_hdr_t {
        unsafe { (*self.inner) }
    }

    pub fn sample_count(&self) -> u32 {
        self.inner().n[htslib::vcf::BCF_DT_SAMPLE as usize] as u32
    }

    pub fn samples(&self) -> Vec<&[u8]> {
        let names = unsafe { slice::from_raw_parts(self.inner().samples, self.sample_count() as usize) };
        names.iter().map(|name| unsafe { ffi::CStr::from_ptr(*name).to_bytes() }).collect()
    }
}
<commit_msg>Add rid2name function to bcf::header<commit_after>// Copyright 2014 Johannes Köster.
// Licensed under the MIT license (http://opensource.org/licenses/MIT)
// This file may not be copied, modified, or distributed
// except according to those terms.

use std::slice;
use std::ffi;

use htslib;

pub type SampleSubset = Vec<i32>;

/// A BCF header.
pub struct Header {
    pub inner: *mut htslib::vcf::bcf_hdr_t,
    pub subset: Option<SampleSubset>,
}

impl Header {
    /// Create a new header.
    pub fn new() -> Self {
        Header {
            inner: unsafe { htslib::vcf::bcf_hdr_init(ffi::CString::new(&b"w"[..]).unwrap().as_ptr()) },
            subset: None,
        }
    }

    pub fn with_template(header: &HeaderView) -> Self {
        Header { inner: unsafe { htslib::vcf::bcf_hdr_dup(header.inner) }, subset: None }
    }

    pub fn subset_template(header: &HeaderView, samples: &[&[u8]]) -> Result<Self, ()> {
        let mut imap = vec![0; samples.len()];
        let names: Vec<_> = samples.iter().map(|&s| ffi::CString::new(s).unwrap()).collect();
        let name_pointers: Vec<_> = names.iter().map(|s| s.as_ptr() as *mut i8).collect();
        let inner = unsafe {
            htslib::vcf::bcf_hdr_subset(header.inner, samples.len() as i32, name_pointers.as_ptr(), imap.as_mut_ptr() as *mut i32)
        };
        if inner.is_null() {
            Err(())
        }
        else {
            Ok(Header { inner: inner, subset: Some(imap) })
        }
    }

    pub fn push_sample(&mut self, sample: &[u8]) -> &mut Self {
        unsafe { htslib::vcf::bcf_hdr_add_sample(self.inner, ffi::CString::new(sample).unwrap().as_ptr()) };
        self
    }

    /// Add a record to the header.
    pub fn push_record(&mut self, record: &[u8]) -> &mut Self {
        unsafe { htslib::vcf::bcf_hdr_append(self.inner, ffi::CString::new(record).unwrap().as_ptr()) };
        self
    }

    pub fn remove_info(&mut self, tag: &[u8]) -> &mut Self {
        unsafe {
            htslib::vcf::bcf_hdr_remove(self.inner, htslib::vcf::BCF_HL_INFO, tag.as_ptr() as *const i8);
        }
        self
    }

    pub fn remove_format(&mut self, tag: &[u8]) -> &mut Self {
        unsafe {
            htslib::vcf::bcf_hdr_remove(self.inner, htslib::vcf::BCF_HL_FMT, tag.as_ptr() as *const i8);
        }
        self
    }
}

impl Drop for Header {
    fn drop(&mut self) {
        unsafe { htslib::vcf::bcf_hdr_destroy(self.inner) };
    }
}

pub struct HeaderView {
    pub inner: *mut htslib::vcf::bcf_hdr_t,
}

impl HeaderView {
    pub fn new(inner: *mut htslib::vcf::bcf_hdr_t) -> Self {
        HeaderView { inner: inner }
    }

    #[inline]
    fn inner(&self) -> htslib::vcf::bcf_hdr_t {
        unsafe { (*self.inner) }
    }

    pub fn sample_count(&self) -> u32 {
        self.inner().n[htslib::vcf::BCF_DT_SAMPLE as usize] as u32
    }

    pub fn samples(&self) -> Vec<&[u8]> {
        let names = unsafe { slice::from_raw_parts(self.inner().samples, self.sample_count() as usize) };
        names.iter().map(|name| unsafe { ffi::CStr::from_ptr(*name).to_bytes() }).collect()
    }

    pub fn rid2name(&self, rid: u32) -> &[u8] {
        unsafe {
            let dict = self.inner().id[htslib::vcf::BCF_DT_CTG as usize];
            let ptr = (*dict.offset(rid as isize)).key;
            ffi::CStr::from_ptr(ptr).to_bytes()
        }
    }
}
<|endoftext|>


<commit_before>// Copyright 2014 Johannes Köster.
// Licensed under the MIT license (http://opensource.org/licenses/MIT)
// This file may not be copied, modified, or distributed
// except according to those terms.

use std::slice;
use std::ffi;

use htslib;

pub type SampleSubset = Vec<i32>;

/// A BCF header.
pub struct Header {
    pub inner: *mut htslib::vcf::bcf_hdr_t,
    pub subset: Option<SampleSubset>,
}

impl Header {
    /// Create a new header.
    pub fn new() -> Self {
        Header {
            inner: unsafe { htslib::vcf::bcf_hdr_init(ffi::CString::new(&b"w"[..]).unwrap().as_ptr()) },
            subset: None,
        }
    }

    pub fn with_template(header: &HeaderView) -> Self {
        Header { inner: unsafe { htslib::vcf::bcf_hdr_dup(header.inner) }, subset: None }
    }

    pub fn subset_template(header: &HeaderView, samples: &[&[u8]]) -> Result<Self, SubsetError> {
        let mut imap = vec![0; samples.len()];
        let names: Vec<_> = samples.iter().map(|&s| ffi::CString::new(s).unwrap()).collect();
        let name_pointers: Vec<_> = names.iter().map(|s| s.as_ptr() as *mut i8).collect();
        let inner = unsafe {
            htslib::vcf::bcf_hdr_subset(header.inner, samples.len() as i32, name_pointers.as_ptr(), imap.as_mut_ptr() as *mut i32)
        };
        if inner.is_null() {
            Err(SubsetError::DuplicateSampleName)
        }
        else {
            Ok(Header { inner: inner, subset: Some(imap) })
        }
    }

    pub fn push_sample(&mut self, sample: &[u8]) -> &mut Self {
        unsafe { htslib::vcf::bcf_hdr_add_sample(self.inner, ffi::CString::new(sample).unwrap().as_ptr()) };
        self
    }

    /// Add a record to the header.
    pub fn push_record(&mut self, record: &[u8]) -> &mut Self {
        unsafe { htslib::vcf::bcf_hdr_append(self.inner, ffi::CString::new(record).unwrap().as_ptr()) };
        self
    }

    pub fn remove_info(&mut self, tag: &[u8]) -> &mut Self {
        unsafe {
            htslib::vcf::bcf_hdr_remove(self.inner, htslib::vcf::BCF_HL_INFO, tag.as_ptr() as *const i8);
        }
        self
    }

    pub fn remove_format(&mut self, tag: &[u8]) -> &mut Self {
        unsafe {
            htslib::vcf::bcf_hdr_remove(self.inner, htslib::vcf::BCF_HL_FMT, tag.as_ptr() as *const i8);
        }
        self
    }
}

impl Drop for Header {
    fn drop(&mut self) {
        unsafe { htslib::vcf::bcf_hdr_destroy(self.inner) };
    }
}

pub struct HeaderView {
    pub inner: *mut htslib::vcf::bcf_hdr_t,
}

impl HeaderView {
    pub fn new(inner: *mut htslib::vcf::bcf_hdr_t) -> Self {
        HeaderView { inner: inner }
    }

    #[inline]
    fn inner(&self) -> htslib::vcf::bcf_hdr_t {
        unsafe { (*self.inner) }
    }

    pub fn sample_count(&self) -> u32 {
        self.inner().n[htslib::vcf::BCF_DT_SAMPLE as usize] as u32
    }

    pub fn samples(&self) -> Vec<&[u8]> {
        let names = unsafe { slice::from_raw_parts(self.inner().samples, self.sample_count() as usize) };
        names.iter().map(|name| unsafe { ffi::CStr::from_ptr(*name).to_bytes() }).collect()
    }

    pub fn rid2name(&self, rid: u32) -> &[u8] {
        unsafe {
            let dict = self.inner().id[htslib::vcf::BCF_DT_CTG as usize];
            let ptr = (*dict.offset(rid as isize)).key;
            ffi::CStr::from_ptr(ptr).to_bytes()
        }
    }
}

quick_error! {
    #[derive(Debug)]
    pub enum SubsetError {
        DuplicateSampleName {
            description("duplicate sample name when subsetting header")
        }
    }
}
<commit_msg>Provide query functions for info and format type.<commit_after>// Copyright 2014 Johannes Köster.
// Licensed under the MIT license (http://opensource.org/licenses/MIT)
// This file may not be copied, modified, or distributed
// except according to those terms.

use std::slice;
use std::ffi;

use htslib;

pub type SampleSubset = Vec<i32>;

/// A BCF header.
pub struct Header {
    pub inner: *mut htslib::vcf::bcf_hdr_t,
    pub subset: Option<SampleSubset>,
}

impl Header {
    /// Create a new header.
    pub fn new() -> Self {
        Header {
            inner: unsafe { htslib::vcf::bcf_hdr_init(ffi::CString::new(&b"w"[..]).unwrap().as_ptr()) },
            subset: None,
        }
    }

    pub fn with_template(header: &HeaderView) -> Self {
        Header { inner: unsafe { htslib::vcf::bcf_hdr_dup(header.inner) }, subset: None }
    }

    pub fn subset_template(header: &HeaderView, samples: &[&[u8]]) -> Result<Self, SubsetError> {
        let mut imap = vec![0; samples.len()];
        let names: Vec<_> = samples.iter().map(|&s| ffi::CString::new(s).unwrap()).collect();
        let name_pointers: Vec<_> = names.iter().map(|s| s.as_ptr() as *mut i8).collect();
        let inner = unsafe {
            htslib::vcf::bcf_hdr_subset(header.inner, samples.len() as i32, name_pointers.as_ptr(), imap.as_mut_ptr() as *mut i32)
        };
        if inner.is_null() {
            Err(SubsetError::DuplicateSampleName)
        }
        else {
            Ok(Header { inner: inner, subset: Some(imap) })
        }
    }

    pub fn push_sample(&mut self, sample: &[u8]) -> &mut Self {
        unsafe { htslib::vcf::bcf_hdr_add_sample(self.inner, ffi::CString::new(sample).unwrap().as_ptr()) };
        self
    }

    /// Add a record to the header.
    pub fn push_record(&mut self, record: &[u8]) -> &mut Self {
        unsafe { htslib::vcf::bcf_hdr_append(self.inner, ffi::CString::new(record).unwrap().as_ptr()) };
        self
    }

    pub fn remove_info(&mut self, tag: &[u8]) -> &mut Self {
        unsafe {
            htslib::vcf::bcf_hdr_remove(self.inner, htslib::vcf::BCF_HL_INFO, tag.as_ptr() as *const i8);
        }
        self
    }

    pub fn remove_format(&mut self, tag: &[u8]) -> &mut Self {
        unsafe {
            htslib::vcf::bcf_hdr_remove(self.inner, htslib::vcf::BCF_HL_FMT, tag.as_ptr() as *const i8);
        }
        self
    }
}

impl Drop for Header {
    fn drop(&mut self) {
        unsafe { htslib::vcf::bcf_hdr_destroy(self.inner) };
    }
}

pub struct HeaderView {
    pub inner: *mut htslib::vcf::bcf_hdr_t,
}

impl HeaderView {
    pub fn new(inner: *mut htslib::vcf::bcf_hdr_t) -> Self {
        HeaderView { inner: inner }
    }

    #[inline]
    fn inner(&self) -> htslib::vcf::bcf_hdr_t {
        unsafe { (*self.inner) }
    }

    pub fn sample_count(&self) -> u32 {
        self.inner().n[htslib::vcf::BCF_DT_SAMPLE as usize] as u32
    }

    pub fn samples(&self) -> Vec<&[u8]> {
        let names = unsafe { slice::from_raw_parts(self.inner().samples, self.sample_count() as usize) };
        names.iter().map(|name| unsafe { ffi::CStr::from_ptr(*name).to_bytes() }).collect()
    }

    pub fn rid2name(&self, rid: u32) -> &[u8] {
        unsafe {
            let dict = self.inner().id[htslib::vcf::BCF_DT_CTG as usize];
            let ptr = (*dict.offset(rid as isize)).key;
            ffi::CStr::from_ptr(ptr).to_bytes()
        }
    }

    pub fn info_type(&self, tag: &[u8]) -> Result<TagType, TagTypeError> {
        self.tag_type(tag, htslib::vcf::BCF_HL_INFO)
    }

    pub fn format_type(&self, tag: &[u8]) -> Result<TagType, TagTypeError> {
        self.tag_type(tag, htslib::vcf::BCF_HL_FMT)
    }

    fn tag_type(&self, tag: &[u8], hdr_type: ::libc::c_int) -> Result<TagType, TagTypeError> {
        let t = unsafe {
            let id = htslib::vcf::bcf_hdr_id2int(
                self.inner,
                htslib::vcf::BCF_DT_ID,
                ffi::CString::new(tag).unwrap().as_ptr() as *mut i8
            );
            let n = (*self.inner).n[htslib::vcf::BCF_DT_ID as usize] as usize;
            let entry = slice::from_raw_parts((*self.inner).id[htslib::vcf::BCF_DT_ID as usize], n);
            let d = (*entry[id as usize].val).info[hdr_type as usize];
            d >> 4 & 0xf // get type as int
        };
        match t as ::libc::c_int {
            htslib::vcf::BCF_HT_FLAG => Ok(TagType::Flag),
            htslib::vcf::BCF_HT_INT => Ok(TagType::Integer),
            htslib::vcf::BCF_HT_REAL => Ok(TagType::Float),
            htslib::vcf::BCF_HT_STR => Ok(TagType::String),
            _ => Err(TagTypeError::UnexpectedTagType)
        }
    }
}

pub enum TagType {
    Flag,
    Integer,
    Float,
    String
}

quick_error! {
    #[derive(Debug)]
    pub enum SubsetError {
        DuplicateSampleName {
            description("duplicate sample name when subsetting header")
        }
    }
}

quick_error! {
    #[derive(Debug)]
    pub enum TagTypeError {
        UnexpectedTagType {
            description("unexpected tag type in header")
        }
    }
}
<|endoftext|>


<commit_before>use std::error::Error;
use std::io;
use std::str;
use std::io::Write;

use itertools::Itertools;
use rust_htslib::bcf;
use rust_htslib::bcf::record::Numeric;

pub struct Writer {
    inner: io::BufWriter<io::Stdout>,
    field_count: usize
}

impl Writer {
    fn new(inner: io::BufWriter<io::Stdout>) -> Self {
        Writer {
            inner: inner,
            field_count: 0
        }
    }

    fn write_integer(&mut self, value: i32) -> Result<(), Box<Error>> {
        self.write_field(format!("{}", value).as_bytes())
    }

    fn write_float(&mut self, value: f32) -> Result<(), Box<Error>> {
        self.write_field(format!("{}", value).as_bytes())
    }

    fn write_flag(&mut self, value: bool) -> Result<(), Box<Error>> {
        self.write_field(format!("{}", value).as_bytes())
    }

    fn write_field(&mut self, value: &[u8]) -> Result<(), Box<Error>> {
        if self.field_count > 0 {
            try!(self.inner.write(b"\t"));
        }
        try!(self.inner.write(value));
        self.field_count += 1;
        Ok(())
    }

    fn newline(&mut self) -> Result<(), Box<Error>> {
        try!(self.inner.write(b"\n"));
        self.field_count = 0;
        Ok(())
    }
}

const HEADER_COMMON: &'static [u8] = b"VARIANT";

pub fn to_txt(
    info_tags: &[&str],
    format_tags: &[&str],
    show_genotypes: bool
) -> Result<(), Box<Error>> {
    let reader = try!(bcf::Reader::new(&"-"));
    let mut writer = Writer::new(io::BufWriter::new(io::stdout()));

    let common_n = 5 + info_tags.len();
    try!(writer.write_field(HEADER_COMMON));
    for _ in 1..common_n {
        try!(writer.write_field(HEADER_COMMON));
    }
    for sample in reader.header.samples() {
        try!(writer.write_field(sample));
        for _ in 1..format_tags.len() + show_genotypes as usize {
            try!(writer.write_field(sample));
        }
    }
    try!(writer.newline());
    try!(writer.write_field(b"CHROM"));
    try!(writer.write_field(b"POS"));
    try!(writer.write_field(b"REF"));
    try!(writer.write_field(b"ALT"));
    try!(writer.write_field(b"QUAL"));
    for name in info_tags {
        try!(writer.write_field(name.as_bytes()));
    }
    for _ in 0..reader.header.sample_count() {
        if show_genotypes {
            try!(writer.write_field(b"GT"));
        }
        for name in format_tags {
            try!(writer.write_field(name.as_bytes()));
        }
    }
    try!(writer.newline());

    let mut record = bcf::Record::new();
    loop {
        if let Err(e) = reader.read(&mut record) {
            if e.is_eof() {
                break;
            } else {
                return Err(Box::new(e));
            }
        }

        let alleles = record.alleles().into_iter().map(|a| a.to_owned()).collect_vec();
        for (i, allele) in alleles[1..].iter().enumerate() {
            try!(writer.write_field(reader.header.rid2name(record.rid().unwrap())));
            try!(writer.write_integer(record.pos() as i32 + 1));
            try!(writer.write_field(&alleles[0]));
            try!(writer.write_field(allele));
            match record.qual() {
                q if q.is_missing() => try!(writer.write_field(b"")),
                q => try!(writer.write_float(q))
            }

            for name in info_tags {
                let _name = name.as_bytes();
                if let Ok((tag_type, tag_length)) = reader.header.info_type(_name) {
                    let i = match tag_length {
                        bcf::header::TagLength::Fixed => {
                            0
                        },
                        bcf::header::TagLength::AltAlleles => {
                            i
                        },
                        _ => return Err(Box::new(ParseError::UnsupportedTagLength))
                    };

                    match tag_type {
                        bcf::header::TagType::Flag => {
                            try!(writer.write_flag(try!(record.info(_name).flag())));
                        },
                        bcf::header::TagType::Integer => {
                            if let Some(values) = try!(record.info(_name).integer()) {
                                try!(writer.write_integer(values[i]));
                            } else {
                                try!(writer.write_field(b""));
                            }
                        },
                        bcf::header::TagType::Float => {
                            if let Some(values) = try!(record.info(_name).float()) {
                                try!(writer.write_float(values[i]));
                            } else {
                                try!(writer.write_field(b""));
                            }
                        },
                        bcf::header::TagType::String => {
                            if let Some(values) = try!(record.info(_name).string()) {
                                try!(writer.write_field(values[i]));
                            } else {
                                try!(writer.write_field(b""));
                            }
                        }
                    }
                } else {
                    // tag undefined, write NA
                    try!(writer.write_field(b""));
                }
            }

            let genotypes = if show_genotypes {
                let genotypes = try!(record.genotypes());
                Some(
                    (0..reader.header.sample_count() as usize).map(|s| {
                        format!("{}", genotypes.get(s))
                    }).collect_vec()
                )
            } else {
                None
            };

            for s in 0..reader.header.sample_count() as usize {
                if let Some(ref genotypes) = genotypes {
                    try!(writer.write_field(genotypes[s].as_bytes()));
                }
                for name in format_tags {
                    let _name = name.as_bytes();
                    if let Ok((tag_type, tag_length)) = reader.header.format_type(_name) {
                        let i = match tag_length {
                            bcf::header::TagLength::Fixed => {
                                0
                            },
                            bcf::header::TagLength::AltAlleles => {
                                i
                            },
                            _ => return Err(Box::new(ParseError::UnsupportedTagLength))
                        };

                        match tag_type {
                            bcf::header::TagType::Flag => {
                                panic!("there is no flag type for format");
                            },
                            bcf::header::TagType::Integer => {
                                try!(writer.write_field(format!("{}", try!(record.format(_name).integer())[s][i]).as_bytes()));
                            },
                            bcf::header::TagType::Float => {
                                try!(writer.write_field(format!("{}", try!(record.format(_name).float())[s][i]).as_bytes()));
                            },
                            bcf::header::TagType::String => {
                                try!(writer.write_field(try!(record.format(_name).string())[s]));
                            }
                        }
                    } else {
                        // tag undefined, write NA
                        try!(writer.write_field(b""));
                    }
                }
            }
            try!(writer.newline());
        }
    }

    Ok(())
}

quick_error! {
    #[derive(Debug)]
    pub enum ParseError {
        UnsupportedTagLength {
            description("currently, only A and 1 are supported multiplicities of tags")
        }

    }
}
<commit_msg>Don't compute index in case of flags.<commit_after>use std::error::Error;
use std::io;
use std::str;
use std::io::Write;

use itertools::Itertools;
use rust_htslib::bcf;
use rust_htslib::bcf::record::Numeric;

pub struct Writer {
    inner: io::BufWriter<io::Stdout>,
    field_count: usize
}

impl Writer {
    fn new(inner: io::BufWriter<io::Stdout>) -> Self {
        Writer {
            inner: inner,
            field_count: 0
        }
    }

    fn write_integer(&mut self, value: i32) -> Result<(), Box<Error>> {
        self.write_field(format!("{}", value).as_bytes())
    }

    fn write_float(&mut self, value: f32) -> Result<(), Box<Error>> {
        self.write_field(format!("{}", value).as_bytes())
    }

    fn write_flag(&mut self, value: bool) -> Result<(), Box<Error>> {
        self.write_field(format!("{}", value).as_bytes())
    }

    fn write_field(&mut self, value: &[u8]) -> Result<(), Box<Error>> {
        if self.field_count > 0 {
            try!(self.inner.write(b"\t"));
        }
        try!(self.inner.write(value));
        self.field_count += 1;
        Ok(())
    }

    fn newline(&mut self) -> Result<(), Box<Error>> {
        try!(self.inner.write(b"\n"));
        self.field_count = 0;
        Ok(())
    }
}

const HEADER_COMMON: &'static [u8] = b"VARIANT";

pub fn to_txt(
    info_tags: &[&str],
    format_tags: &[&str],
    show_genotypes: bool
) -> Result<(), Box<Error>> {
    let reader = try!(bcf::Reader::new(&"-"));
    let mut writer = Writer::new(io::BufWriter::new(io::stdout()));

    let common_n = 5 + info_tags.len();
    try!(writer.write_field(HEADER_COMMON));
    for _ in 1..common_n {
        try!(writer.write_field(HEADER_COMMON));
    }
    for sample in reader.header.samples() {
        try!(writer.write_field(sample));
        for _ in 1..format_tags.len() + show_genotypes as usize {
            try!(writer.write_field(sample));
        }
    }
    try!(writer.newline());
    try!(writer.write_field(b"CHROM"));
    try!(writer.write_field(b"POS"));
    try!(writer.write_field(b"REF"));
    try!(writer.write_field(b"ALT"));
    try!(writer.write_field(b"QUAL"));
    for name in info_tags {
        try!(writer.write_field(name.as_bytes()));
    }
    for _ in 0..reader.header.sample_count() {
        if show_genotypes {
            try!(writer.write_field(b"GT"));
        }
        for name in format_tags {
            try!(writer.write_field(name.as_bytes()));
        }
    }
    try!(writer.newline());

    let mut record = bcf::Record::new();
    loop {
        if let Err(e) = reader.read(&mut record) {
            if e.is_eof() {
                break;
            } else {
                return Err(Box::new(e));
            }
        }

        let alleles = record.alleles().into_iter().map(|a| a.to_owned()).collect_vec();
        for (i, allele) in alleles[1..].iter().enumerate() {
            try!(writer.write_field(reader.header.rid2name(record.rid().unwrap())));
            try!(writer.write_integer(record.pos() as i32 + 1));
            try!(writer.write_field(&alleles[0]));
            try!(writer.write_field(allele));
            match record.qual() {
                q if q.is_missing() => try!(writer.write_field(b"")),
                q => try!(writer.write_float(q))
            }

            for name in info_tags {
                let _name = name.as_bytes();
                if let Ok((tag_type, tag_length)) = reader.header.info_type(_name) {
                    let get_idx = || {
                        match tag_length {
                            bcf::header::TagLength::Fixed => {
                                Ok(0)
                            },
                            bcf::header::TagLength::AltAlleles => {
                                Ok(i)
                            },
                            _ => Err(Box::new(ParseError::UnsupportedTagLength))
                        }
                    };

                    match tag_type {
                        bcf::header::TagType::Flag => {
                            try!(writer.write_flag(try!(record.info(_name).flag())));
                        },
                        bcf::header::TagType::Integer => {
                            let i = try!(get_idx());
                            if let Some(values) = try!(record.info(_name).integer()) {
                                try!(writer.write_integer(values[i]));
                            } else {
                                try!(writer.write_field(b""));
                            }
                        },
                        bcf::header::TagType::Float => {
                            let i = try!(get_idx());
                            if let Some(values) = try!(record.info(_name).float()) {
                                try!(writer.write_float(values[i]));
                            } else {
                                try!(writer.write_field(b""));
                            }
                        },
                        bcf::header::TagType::String => {
                            let i = try!(get_idx());
                            if let Some(values) = try!(record.info(_name).string()) {
                                try!(writer.write_field(values[i]));
                            } else {
                                try!(writer.write_field(b""));
                            }
                        }
                    }
                } else {
                    // tag undefined, write NA
                    try!(writer.write_field(b""));
                }
            }

            let genotypes = if show_genotypes {
                let genotypes = try!(record.genotypes());
                Some(
                    (0..reader.header.sample_count() as usize).map(|s| {
                        format!("{}", genotypes.get(s))
                    }).collect_vec()
                )
            } else {
                None
            };

            for s in 0..reader.header.sample_count() as usize {
                if let Some(ref genotypes) = genotypes {
                    try!(writer.write_field(genotypes[s].as_bytes()));
                }
                for name in format_tags {
                    let _name = name.as_bytes();
                    if let Ok((tag_type, tag_length)) = reader.header.format_type(_name) {
                        let i = match tag_length {
                            bcf::header::TagLength::Fixed => {
                                0
                            },
                            bcf::header::TagLength::AltAlleles => {
                                i
                            },
                            _ => return Err(Box::new(ParseError::UnsupportedTagLength))
                        };

                        match tag_type {
                            bcf::header::TagType::Flag => {
                                panic!("there is no flag type for format");
                            },
                            bcf::header::TagType::Integer => {
                                try!(writer.write_field(format!("{}", try!(record.format(_name).integer())[s][i]).as_bytes()));
                            },
                            bcf::header::TagType::Float => {
                                try!(writer.write_field(format!("{}", try!(record.format(_name).float())[s][i]).as_bytes()));
                            },
                            bcf::header::TagType::String => {
                                try!(writer.write_field(try!(record.format(_name).string())[s]));
                            }
                        }
                    } else {
                        // tag undefined, write NA
                        try!(writer.write_field(b""));
                    }
                }
            }
            try!(writer.newline());
        }
    }

    Ok(())
}

quick_error! {
    #[derive(Debug)]
    pub enum ParseError {
        UnsupportedTagLength {
            description("currently, only A and 1 are supported multiplicities of tags")
        }

    }
}
<|endoftext|>