<commit_before>//! LossyCounter implementation.

use std::collections::hash_map::{Entry, HashMap};
use std::hash::Hash;

#[derive(Debug)]
struct KnownEntry {
    f: usize,
    delta: usize,
}

/// TODO
///
/// # References
/// - ["Approximate Frequency Counts over Data Streams", Gurmeet S. Manku, Rajeev Motwani, 2002](https://www.vldb.org/conf/2002/S10P03.pdf)
/// - [Wikipedia: Lossy Count Algorithm](https://en.wikipedia.org/wiki/Lossy_Count_Algorithm)
/// - ["Top K Frequent Items Algorithm", Zhipeng Jiang, 2017](https://zpjiang.me/2017/11/13/top-k-elementes-system-design/)
#[derive(Debug)]
pub struct LossyCounter<T>
where
    T: Clone + Eq + Hash,
{
    epsilon: f64,
    known: HashMap<T, KnownEntry>,
    n: usize,
    width: usize,
}

impl<T> LossyCounter<T>
where
    T: Clone + Eq + Hash,
{
    /// Creates new lossy counter so that for reported frequencies `f_e` it holds:
    ///
    /// ```text
    /// f <= f_e <= epsilon * n
    /// ```
    ///
    /// where `f` is the true frequency and `n` the number of data points seen so far.
    pub fn with_properties(epsilon: f64) -> Self {
        assert!(
            (epsilon > 0.) & (epsilon < 1.),
            "epsilon ({}) must be greater than 0 and smaller than 1",
            epsilon
        );

        Self {
            epsilon,
            known: HashMap::new(),
            n: 0,
            width: (1. / epsilon).ceil() as usize,
        }
    }

    /// Epsilon error.
    pub fn epsilon(&self) -> f64 {
        self.epsilon
    }

    /// Number of data points seen so far.
    pub fn n(&self) -> usize {
        self.n
    }

    /// Add new element to the counter.
    ///
    /// Reports, if the element was considered new (`true`) or not (`false`).
    pub fn add(&mut self, t: T) -> bool {
        // pre-inc, since "N denotes the current length of the stream"
        self.n += 1;

        let b_current = self.n / self.width + 1;

        // add new data
        let was_new = match self.known.entry(t) {
            Entry::Occupied(mut o) => {
                let mut value = o.get_mut();
                value.f += 1;
                false
            }
            Entry::Vacant(v) => {
                v.insert(KnownEntry {
                    f: 1,
                    delta: b_current - 1,
                });
                true
            }
        };

        // pruning
        if self.n % self.width == 0 {
            // FIXME: nll
            let tmp: HashMap<T, KnownEntry> = self
                .known
                .drain()
                .filter(|(_k, v)| v.f + v.delta > b_current)
                .collect();
            self.known = tmp;
        }

        was_new
    }

    /// Query elements for which, with their guessed frequency `f`, it holds:
    ///
    /// ```text
    /// f >= (threshold - epsilon) * n
    /// ```
    ///
    /// The order of the elements is undefined.
    pub fn query<'a>(&'a self, threshold: f64) -> impl 'a + Iterator<Item = T> {
        let bound = (((threshold - self.epsilon) * (self.n as f64)).ceil()).max(0.) as usize;
        self.known
            .iter()
            .filter(move |(_k, v)| v.f >= bound)
            .map(|(k, _v)| k)
            .cloned()
    }
}

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

    #[test]
    fn new() {
        let counter = LossyCounter::<u64>::with_properties(0.5);
        assert_eq!(counter.epsilon(), 0.5);
        assert_eq!(counter.n(), 0);
    }

    #[test]
    #[should_panic(expected = "epsilon (0) must be greater than 0 and smaller than 1")]
    fn new_panics_epsilon_0() {
        LossyCounter::<u64>::with_properties(0.);
    }

    #[test]
    #[should_panic(expected = "epsilon (1) must be greater than 0 and smaller than 1")]
    fn new_panics_epsilon_1() {
        LossyCounter::<u64>::with_properties(1.);
    }

    #[test]
    fn add() {
        let mut counter = LossyCounter::<u64>::with_properties(0.5);
        assert!(counter.add(13));
        assert_eq!(counter.n(), 1);
    }

    #[test]
    fn double_add() {
        let mut counter = LossyCounter::<u64>::with_properties(0.5);
        assert!(counter.add(13));
        assert!(!counter.add(13));
        assert_eq!(counter.n(), 2);
    }

    #[test]
    fn query_all() {
        let mut counter = LossyCounter::<u64>::with_properties(0.2);
        assert!(counter.add(13));
        assert!(counter.add(42));

        let mut data: Vec<u64> = counter.query(0.).collect();
        data.sort();
        assert_eq!(data, vec![13, 42]);
    }

    #[test]
    fn query_part() {
        let mut counter = LossyCounter::<u64>::with_properties(0.001);
        assert!(counter.add(13));
        assert!(!counter.add(13));
        assert!(counter.add(42));

        let mut data: Vec<u64> = counter.query(0.6).collect();
        data.sort();
        assert_eq!(data, vec![13]);
    }

    #[test]
    fn query_large() {
        let mut counter = LossyCounter::<u64>::with_properties(0.01);
        for i in 0..1000 {
            let j = i % 10;
            if j <= 6 {
                counter.add(i);
            } else {
                counter.add(j);
            }
        }

        let mut data: Vec<u64> = counter.query(0.02).collect();
        data.sort();
        assert_eq!(data, vec![7, 8, 9]);
    }
}
<commit_msg>lossycounter: use NLL<commit_after>//! LossyCounter implementation.

use std::collections::hash_map::{Entry, HashMap};
use std::hash::Hash;

#[derive(Debug)]
struct KnownEntry {
    f: usize,
    delta: usize,
}

/// TODO
///
/// # References
/// - ["Approximate Frequency Counts over Data Streams", Gurmeet S. Manku, Rajeev Motwani, 2002](https://www.vldb.org/conf/2002/S10P03.pdf)
/// - [Wikipedia: Lossy Count Algorithm](https://en.wikipedia.org/wiki/Lossy_Count_Algorithm)
/// - ["Top K Frequent Items Algorithm", Zhipeng Jiang, 2017](https://zpjiang.me/2017/11/13/top-k-elementes-system-design/)
#[derive(Debug)]
pub struct LossyCounter<T>
where
    T: Clone + Eq + Hash,
{
    epsilon: f64,
    known: HashMap<T, KnownEntry>,
    n: usize,
    width: usize,
}

impl<T> LossyCounter<T>
where
    T: Clone + Eq + Hash,
{
    /// Creates new lossy counter so that for reported frequencies `f_e` it holds:
    ///
    /// ```text
    /// f <= f_e <= epsilon * n
    /// ```
    ///
    /// where `f` is the true frequency and `n` the number of data points seen so far.
    pub fn with_properties(epsilon: f64) -> Self {
        assert!(
            (epsilon > 0.) & (epsilon < 1.),
            "epsilon ({}) must be greater than 0 and smaller than 1",
            epsilon
        );

        Self {
            epsilon,
            known: HashMap::new(),
            n: 0,
            width: (1. / epsilon).ceil() as usize,
        }
    }

    /// Epsilon error.
    pub fn epsilon(&self) -> f64 {
        self.epsilon
    }

    /// Number of data points seen so far.
    pub fn n(&self) -> usize {
        self.n
    }

    /// Add new element to the counter.
    ///
    /// Reports, if the element was considered new (`true`) or not (`false`).
    pub fn add(&mut self, t: T) -> bool {
        // pre-inc, since "N denotes the current length of the stream"
        self.n += 1;

        let b_current = self.n / self.width + 1;

        // add new data
        let was_new = match self.known.entry(t) {
            Entry::Occupied(mut o) => {
                let mut value = o.get_mut();
                value.f += 1;
                false
            }
            Entry::Vacant(v) => {
                v.insert(KnownEntry {
                    f: 1,
                    delta: b_current - 1,
                });
                true
            }
        };

        // pruning
        if self.n % self.width == 0 {
            self.known = self
                .known
                .drain()
                .filter(|(_k, v)| v.f + v.delta > b_current)
                .collect();
        }

        was_new
    }

    /// Query elements for which, with their guessed frequency `f`, it holds:
    ///
    /// ```text
    /// f >= (threshold - epsilon) * n
    /// ```
    ///
    /// The order of the elements is undefined.
    pub fn query<'a>(&'a self, threshold: f64) -> impl 'a + Iterator<Item = T> {
        let bound = (((threshold - self.epsilon) * (self.n as f64)).ceil()).max(0.) as usize;
        self.known
            .iter()
            .filter(move |(_k, v)| v.f >= bound)
            .map(|(k, _v)| k)
            .cloned()
    }
}

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

    #[test]
    fn new() {
        let counter = LossyCounter::<u64>::with_properties(0.5);
        assert_eq!(counter.epsilon(), 0.5);
        assert_eq!(counter.n(), 0);
    }

    #[test]
    #[should_panic(expected = "epsilon (0) must be greater than 0 and smaller than 1")]
    fn new_panics_epsilon_0() {
        LossyCounter::<u64>::with_properties(0.);
    }

    #[test]
    #[should_panic(expected = "epsilon (1) must be greater than 0 and smaller than 1")]
    fn new_panics_epsilon_1() {
        LossyCounter::<u64>::with_properties(1.);
    }

    #[test]
    fn add() {
        let mut counter = LossyCounter::<u64>::with_properties(0.5);
        assert!(counter.add(13));
        assert_eq!(counter.n(), 1);
    }

    #[test]
    fn double_add() {
        let mut counter = LossyCounter::<u64>::with_properties(0.5);
        assert!(counter.add(13));
        assert!(!counter.add(13));
        assert_eq!(counter.n(), 2);
    }

    #[test]
    fn query_all() {
        let mut counter = LossyCounter::<u64>::with_properties(0.2);
        assert!(counter.add(13));
        assert!(counter.add(42));

        let mut data: Vec<u64> = counter.query(0.).collect();
        data.sort();
        assert_eq!(data, vec![13, 42]);
    }

    #[test]
    fn query_part() {
        let mut counter = LossyCounter::<u64>::with_properties(0.001);
        assert!(counter.add(13));
        assert!(!counter.add(13));
        assert!(counter.add(42));

        let mut data: Vec<u64> = counter.query(0.6).collect();
        data.sort();
        assert_eq!(data, vec![13]);
    }

    #[test]
    fn query_large() {
        let mut counter = LossyCounter::<u64>::with_properties(0.01);
        for i in 0..1000 {
            let j = i % 10;
            if j <= 6 {
                counter.add(i);
            } else {
                counter.add(j);
            }
        }

        let mut data: Vec<u64> = counter.query(0.02).collect();
        data.sort();
        assert_eq!(data, vec![7, 8, 9]);
    }
}
<|endoftext|>


<commit_before>use config::{parse_config, Benchmark, Config};
use Commit;
use git2;
use Push;
use repo::Workspace;

use std::collections::HashMap;
use std::io;
use std::path::Path;
use std::process::{Command, ExitStatus, Output};
use std::str;

/// `(val, percentage_change)`
#[derive(Debug, Clone)]
pub enum BenchmarkResult<T> {
    Improvement(T, f64),
    Regression(T, f64),
    Neutral(T, f64),
}

#[derive(Debug, Clone)]
pub struct TastingResult {
    pub branch: Option<String>,
    pub commit: Commit,
    pub build: bool,
    pub test: bool,
    pub bench: bool,
    pub results: Option<Vec<(Benchmark, ExitStatus, HashMap<String, BenchmarkResult<f64>>)>>,
}

fn run_benchmark(workdir: &str, cfg: &Config, bench: &Benchmark, timeout: Option<u64>) -> Output {
    let mut cmd = if cfg.version.is_none() || cfg.version.unwrap() < 2 {
        // older taster configs assume an implied "cargo" prefix on each benchmark command
        match timeout {
            None => {
                let mut cmd = Command::new("cargo");
                cmd.arg(&bench.cmd);
                cmd
            }
            Some(timeout) => {
                let mut cmd = Command::new("timeout");
                cmd.arg("-k")
                    .arg(&format!("{}s", timeout + 30))
                    .arg(&format!("{}s", timeout))
                    .arg("cargo")
                    .arg(&bench.cmd);
                cmd
            }
        }
    } else {
        // from taster config version 2, we no longer assume an implicit "cargo" prefix on
        // benchmark commands
        match timeout {
            None => Command::new(&bench.cmd),
            Some(timeout) => {
                let mut cmd = Command::new("timeout");
                cmd.arg("-k")
                    .arg(&format!("{}s", timeout + 30))
                    .arg(&format!("{}s", timeout))
                    .arg(&bench.cmd);
                cmd
            }
        }
    };

    cmd.current_dir(workdir)
        .env("RUST_BACKTRACE", "1")
        .args(bench.args.as_slice());

    cmd.output()
        .expect(&format!("Failed to execute benchmark '{}'!", bench.name))
}

fn write_output(output: &Output, commit_id: git2::Oid, name: &str) {
    use std::fs::File;
    use std::io::Write;

    let mut stdout_file =
        File::create(&format!("{}-{}-stdout.log", commit_id, name)).expect(&format!(
            "Failed to create stdout log file for '{}' at commit '{}'.",
            name, commit_id
        ));
    stdout_file
        .write_all(output.stdout.as_slice())
        .expect("Failed to write output to stdout log file!");
    let mut stderr_file =
        File::create(&format!("{}-{}-stderr.log", commit_id, name)).expect(&format!(
            "Failed to create stderr log file for '{}' at commit '{}'.",
            name, commit_id
        ));
    stderr_file
        .write_all(output.stderr.as_slice())
        .expect("Failed to write output to stderr log file!");
}

fn benchmark(
    workdir: &str,
    cfg: &Config,
    bench: &Benchmark,
    commit_id: git2::Oid,
    previous_result: Option<&HashMap<String, BenchmarkResult<f64>>>,
    timeout: Option<u64>,
) -> (ExitStatus, HashMap<String, BenchmarkResult<f64>>) {
    // Run the benchmark and collect its output
    let output = run_benchmark(workdir, cfg, bench, timeout);
    write_output(&output, commit_id, &bench.name);

    let lines = str::from_utf8(output.stdout.as_slice())
        .unwrap()
        .lines()
        .chain(str::from_utf8(output.stderr.as_slice()).unwrap().lines());
    let mut res = HashMap::new();

    // Don't try parsing the output if we didn't succeed
    if !output.status.success() {
        return (output.status, res);
    }

    // Success, so let's look for the results
    for l in lines {
        for (i, regex) in bench.result_expr.iter().enumerate() {
            for cap in regex.captures_iter(l) {
                let (metric, value) = if cap.len() > 2 {
                    (String::from(cap.at(1).unwrap()), cap.at(2))
                } else {
                    (format!("{}", i), cap.at(1))
                };
                let bm_name = format!("{}/{}", bench.name, &metric);
                if let Some(c) = value {
                    use std::str::FromStr;
                    let val = match f64::from_str(&c) {
                        Ok(f) => f,
                        Err(_) => {
                            println!(
                                "failed to parse value '{}' for {} into f64 number, ignoring",
                                c, bm_name
                            );
                            continue;
                        }
                    };
                    let new_result = match previous_result {
                        None => BenchmarkResult::Improvement(val, 0.0),
                        Some(prev_res) => {
                            let old_val = match prev_res.get(&bm_name) {
                                None => val,
                                Some(pv) => match *pv {
                                    BenchmarkResult::Improvement(v, _) => v,
                                    BenchmarkResult::Regression(v, _) => v,
                                    BenchmarkResult::Neutral(v, _) => v,
                                },
                            };
                            let new_result = if bench.lower_is_better {
                                if val >= old_val * (1.0 + bench.regression_threshold) {
                                    BenchmarkResult::Regression(val, (val / old_val) - 1.0)
                                } else if val < old_val * (1.0 - bench.improvement_threshold) {
                                    BenchmarkResult::Improvement(val, (val / old_val) - 1.0)
                                } else {
                                    BenchmarkResult::Neutral(val, (val / old_val) - 1.0)
                                }
                            } else {
                                if val >= old_val * (1.0 + bench.improvement_threshold) {
                                    BenchmarkResult::Improvement(val, (val / old_val) - 1.0)
                                } else if val < old_val * (1.0 - bench.regression_threshold) {
                                    BenchmarkResult::Regression(val, (val / old_val) - 1.0)
                                } else {
                                    BenchmarkResult::Neutral(val, (val / old_val) - 1.0)
                                }
                            };
                            new_result
                        }
                    };
                    res.insert(bm_name, new_result);
                }
            }
        }
    }
    (output.status, res)
}

fn build(workdir: &str) -> Output {
    Command::new("cargo")
        .current_dir(workdir)
        .arg("check")
        .arg("--all")
        .arg("--all-targets")
        .env("RUST_BACKTRACE", "1")
        .output()
        .expect("Failed to execute 'cargo build'!")
}

pub fn taste_commit(
    ws: &Workspace,
    history: &mut HashMap<String, HashMap<String, HashMap<String, BenchmarkResult<f64>>>>,
    push: &Push,
    commit: &Commit,
    def_improvement_threshold: f64,
    def_regression_threshold: f64,
    timeout: Option<u64>,
) -> Result<(Option<Config>, TastingResult), String> {
    println!("Tasting commit {}", commit.id);
    ws.checkout_commit(&commit.id)?;

    let branch = match push.push_ref {
        None => None,
        Some(ref pr) => match pr.rfind("/") {
            None => None,
            Some(i) => Some(String::from(&pr[i + 1..])),
        },
    };

    let version_output = version(&ws.path);
    write_output(&version_output, commit.id, "version");

    let do_update = !Path::new(&format!("{}/Cargo.lock", ws.path)).exists();

    let build_success = {
        let update_success = if do_update {
            println!("running 'cargo update'");
            let update_output = update(&ws.path);
            write_output(&update_output, commit.id, "update");
            if !update_output.status.success() {
                println!("update failed: output status is {:?}", update_output.status);
            }
            update_output.status.success()
        } else {
            // nothing to do, always succeeds
            true
        };

        let build_output = build(&ws.path);
        write_output(&build_output, commit.id, "build");
        if !build_output.status.success() {
            println!("build failed: output status is {:?}", build_output.status);
        }

        update_success && build_output.status.success()
    };

    let test_output = test(&ws.path, timeout);
    write_output(&test_output, commit.id, "test");

    if !test_output.status.success() {
        println!("tests failed: output status is {:?}", test_output.status);
    }

    let cfg = match parse_config(
        Path::new(&format!("{}/taster.toml", ws.path)),
        def_improvement_threshold,
        def_regression_threshold,
    ) {
        Ok(c) => c,
        Err(e) => match e.kind() {
            io::ErrorKind::NotFound => {
                println!(
                    "Skipping commit {} which doesn't have a Taster config.",
                    commit.id
                );
                return Ok((
                    None,
                    TastingResult {
                        branch: branch,
                        commit: commit.clone(),
                        build: build_success,
                        test: test_output.status.success(),
                        bench: false,
                        results: None,
                    },
                ));
            }
            io::ErrorKind::InvalidInput => {
                println!(
                    "Skipping commit {} which has an invalid Taster config.",
                    commit.id
                );
                return Ok((
                    None,
                    TastingResult {
                        branch: branch,
                        commit: commit.clone(),
                        build: build_success,
                        test: test_output.status.success(),
                        bench: false,
                        results: None,
                    },
                ));
            }
            _ => unimplemented!(),
        },
    };

    let bench_results =
        match branch {
            Some(ref branch) => {
                let branch_history = history.entry(branch.clone()).or_insert(HashMap::new());
                cfg.benchmarks
                .iter()
                .map(|b| {
                    let (status, res) =
                        benchmark(&ws.path, &cfg, b, commit.id, branch_history.get(&b.name),
                                  timeout);
                    branch_history.insert(b.name.clone(), res.clone());
                    (b.clone(), status, res)
                })
                .collect::<Vec<(Benchmark, ExitStatus, HashMap<String, BenchmarkResult<f64>>)>>()
            }
            None => cfg.benchmarks
                .iter()
                .map(|b| {
                    let (status, res) = benchmark(&ws.path, &cfg, b, commit.id, None, timeout);
                    (b.clone(), status, res)
                })
                .collect::<Vec<(Benchmark, ExitStatus, HashMap<String, BenchmarkResult<f64>>)>>(),
        };
    let bench_success = bench_results.iter().all(|x| x.1.success());

    Ok((
        Some(cfg),
        TastingResult {
            branch: branch,
            commit: commit.clone(),
            build: build_success,
            test: test_output.status.success(),
            bench: bench_success,
            results: Some(bench_results),
        },
    ))
}

fn test(workdir: &str, timeout: Option<u64>) -> Output {
    let mut cmd = match timeout {
        None => {
            let mut cmd = Command::new("cargo");
            cmd.arg("test").arg("--all");
            cmd
        }
        Some(timeout) => {
            let mut cmd = Command::new("timeout");
            cmd.arg("-k")
                .arg(&format!("{}s", timeout + 30))
                .arg(&format!("{}s", timeout))
                .arg("cargo")
                .arg("test")
                .arg("--all");
            cmd
        }
    };
    cmd.current_dir(workdir)
        .env("RUST_BACKTRACE", "1")
        .env("RUST_TEST_THREADS", "1")
        .output()
        .expect("Failed to execute 'cargo test'!")
}

fn update(workdir: &str) -> Output {
    Command::new("cargo")
        .current_dir(workdir)
        .arg("update")
        .output()
        .expect("Failed to execute 'cargo update'!")
}

fn version(workdir: &str) -> Output {
    Command::new("rustc")
        .current_dir(workdir)
        .arg("--version")
        .output()
        .expect("Failed to execute 'rustc --version'!")
}
<commit_msg>rustfmt<commit_after>use config::{parse_config, Benchmark, Config};
use git2;
use repo::Workspace;
use Commit;
use Push;

use std::collections::HashMap;
use std::io;
use std::path::Path;
use std::process::{Command, ExitStatus, Output};
use std::str;

/// `(val, percentage_change)`
#[derive(Debug, Clone)]
pub enum BenchmarkResult<T> {
    Improvement(T, f64),
    Regression(T, f64),
    Neutral(T, f64),
}

#[derive(Debug, Clone)]
pub struct TastingResult {
    pub branch: Option<String>,
    pub commit: Commit,
    pub build: bool,
    pub test: bool,
    pub bench: bool,
    pub results: Option<Vec<(Benchmark, ExitStatus, HashMap<String, BenchmarkResult<f64>>)>>,
}

fn run_benchmark(workdir: &str, cfg: &Config, bench: &Benchmark, timeout: Option<u64>) -> Output {
    let mut cmd = if cfg.version.is_none() || cfg.version.unwrap() < 2 {
        // older taster configs assume an implied "cargo" prefix on each benchmark command
        match timeout {
            None => {
                let mut cmd = Command::new("cargo");
                cmd.arg(&bench.cmd);
                cmd
            }
            Some(timeout) => {
                let mut cmd = Command::new("timeout");
                cmd.arg("-k")
                    .arg(&format!("{}s", timeout + 30))
                    .arg(&format!("{}s", timeout))
                    .arg("cargo")
                    .arg(&bench.cmd);
                cmd
            }
        }
    } else {
        // from taster config version 2, we no longer assume an implicit "cargo" prefix on
        // benchmark commands
        match timeout {
            None => Command::new(&bench.cmd),
            Some(timeout) => {
                let mut cmd = Command::new("timeout");
                cmd.arg("-k")
                    .arg(&format!("{}s", timeout + 30))
                    .arg(&format!("{}s", timeout))
                    .arg(&bench.cmd);
                cmd
            }
        }
    };

    cmd.current_dir(workdir)
        .env("RUST_BACKTRACE", "1")
        .args(bench.args.as_slice());

    cmd.output()
        .expect(&format!("Failed to execute benchmark '{}'!", bench.name))
}

fn write_output(output: &Output, commit_id: git2::Oid, name: &str) {
    use std::fs::File;
    use std::io::Write;

    let mut stdout_file =
        File::create(&format!("{}-{}-stdout.log", commit_id, name)).expect(&format!(
            "Failed to create stdout log file for '{}' at commit '{}'.",
            name, commit_id
        ));
    stdout_file
        .write_all(output.stdout.as_slice())
        .expect("Failed to write output to stdout log file!");
    let mut stderr_file =
        File::create(&format!("{}-{}-stderr.log", commit_id, name)).expect(&format!(
            "Failed to create stderr log file for '{}' at commit '{}'.",
            name, commit_id
        ));
    stderr_file
        .write_all(output.stderr.as_slice())
        .expect("Failed to write output to stderr log file!");
}

fn benchmark(
    workdir: &str,
    cfg: &Config,
    bench: &Benchmark,
    commit_id: git2::Oid,
    previous_result: Option<&HashMap<String, BenchmarkResult<f64>>>,
    timeout: Option<u64>,
) -> (ExitStatus, HashMap<String, BenchmarkResult<f64>>) {
    // Run the benchmark and collect its output
    let output = run_benchmark(workdir, cfg, bench, timeout);
    write_output(&output, commit_id, &bench.name);

    let lines = str::from_utf8(output.stdout.as_slice())
        .unwrap()
        .lines()
        .chain(str::from_utf8(output.stderr.as_slice()).unwrap().lines());
    let mut res = HashMap::new();

    // Don't try parsing the output if we didn't succeed
    if !output.status.success() {
        return (output.status, res);
    }

    // Success, so let's look for the results
    for l in lines {
        for (i, regex) in bench.result_expr.iter().enumerate() {
            for cap in regex.captures_iter(l) {
                let (metric, value) = if cap.len() > 2 {
                    (String::from(cap.at(1).unwrap()), cap.at(2))
                } else {
                    (format!("{}", i), cap.at(1))
                };
                let bm_name = format!("{}/{}", bench.name, &metric);
                if let Some(c) = value {
                    use std::str::FromStr;
                    let val = match f64::from_str(&c) {
                        Ok(f) => f,
                        Err(_) => {
                            println!(
                                "failed to parse value '{}' for {} into f64 number, ignoring",
                                c, bm_name
                            );
                            continue;
                        }
                    };
                    let new_result = match previous_result {
                        None => BenchmarkResult::Improvement(val, 0.0),
                        Some(prev_res) => {
                            let old_val = match prev_res.get(&bm_name) {
                                None => val,
                                Some(pv) => match *pv {
                                    BenchmarkResult::Improvement(v, _) => v,
                                    BenchmarkResult::Regression(v, _) => v,
                                    BenchmarkResult::Neutral(v, _) => v,
                                },
                            };
                            let new_result = if bench.lower_is_better {
                                if val >= old_val * (1.0 + bench.regression_threshold) {
                                    BenchmarkResult::Regression(val, (val / old_val) - 1.0)
                                } else if val < old_val * (1.0 - bench.improvement_threshold) {
                                    BenchmarkResult::Improvement(val, (val / old_val) - 1.0)
                                } else {
                                    BenchmarkResult::Neutral(val, (val / old_val) - 1.0)
                                }
                            } else {
                                if val >= old_val * (1.0 + bench.improvement_threshold) {
                                    BenchmarkResult::Improvement(val, (val / old_val) - 1.0)
                                } else if val < old_val * (1.0 - bench.regression_threshold) {
                                    BenchmarkResult::Regression(val, (val / old_val) - 1.0)
                                } else {
                                    BenchmarkResult::Neutral(val, (val / old_val) - 1.0)
                                }
                            };
                            new_result
                        }
                    };
                    res.insert(bm_name, new_result);
                }
            }
        }
    }
    (output.status, res)
}

fn build(workdir: &str) -> Output {
    Command::new("cargo")
        .current_dir(workdir)
        .arg("check")
        .arg("--all")
        .arg("--all-targets")
        .env("RUST_BACKTRACE", "1")
        .output()
        .expect("Failed to execute 'cargo build'!")
}

pub fn taste_commit(
    ws: &Workspace,
    history: &mut HashMap<String, HashMap<String, HashMap<String, BenchmarkResult<f64>>>>,
    push: &Push,
    commit: &Commit,
    def_improvement_threshold: f64,
    def_regression_threshold: f64,
    timeout: Option<u64>,
) -> Result<(Option<Config>, TastingResult), String> {
    println!("Tasting commit {}", commit.id);
    ws.checkout_commit(&commit.id)?;

    let branch = match push.push_ref {
        None => None,
        Some(ref pr) => match pr.rfind("/") {
            None => None,
            Some(i) => Some(String::from(&pr[i + 1..])),
        },
    };

    let version_output = version(&ws.path);
    write_output(&version_output, commit.id, "version");

    let do_update = !Path::new(&format!("{}/Cargo.lock", ws.path)).exists();

    let build_success = {
        let update_success = if do_update {
            println!("running 'cargo update'");
            let update_output = update(&ws.path);
            write_output(&update_output, commit.id, "update");
            if !update_output.status.success() {
                println!("update failed: output status is {:?}", update_output.status);
            }
            update_output.status.success()
        } else {
            // nothing to do, always succeeds
            true
        };

        let build_output = build(&ws.path);
        write_output(&build_output, commit.id, "build");
        if !build_output.status.success() {
            println!("build failed: output status is {:?}", build_output.status);
        }

        update_success && build_output.status.success()
    };

    let test_output = test(&ws.path, timeout);
    write_output(&test_output, commit.id, "test");

    if !test_output.status.success() {
        println!("tests failed: output status is {:?}", test_output.status);
    }

    let cfg = match parse_config(
        Path::new(&format!("{}/taster.toml", ws.path)),
        def_improvement_threshold,
        def_regression_threshold,
    ) {
        Ok(c) => c,
        Err(e) => match e.kind() {
            io::ErrorKind::NotFound => {
                println!(
                    "Skipping commit {} which doesn't have a Taster config.",
                    commit.id
                );
                return Ok((
                    None,
                    TastingResult {
                        branch: branch,
                        commit: commit.clone(),
                        build: build_success,
                        test: test_output.status.success(),
                        bench: false,
                        results: None,
                    },
                ));
            }
            io::ErrorKind::InvalidInput => {
                println!(
                    "Skipping commit {} which has an invalid Taster config.",
                    commit.id
                );
                return Ok((
                    None,
                    TastingResult {
                        branch: branch,
                        commit: commit.clone(),
                        build: build_success,
                        test: test_output.status.success(),
                        bench: false,
                        results: None,
                    },
                ));
            }
            _ => unimplemented!(),
        },
    };

    let bench_results = match branch {
        Some(ref branch) => {
            let branch_history = history.entry(branch.clone()).or_insert(HashMap::new());
            cfg.benchmarks
                .iter()
                .map(|b| {
                    let (status, res) = benchmark(
                        &ws.path,
                        &cfg,
                        b,
                        commit.id,
                        branch_history.get(&b.name),
                        timeout,
                    );
                    branch_history.insert(b.name.clone(), res.clone());
                    (b.clone(), status, res)
                })
                .collect::<Vec<(Benchmark, ExitStatus, HashMap<String, BenchmarkResult<f64>>)>>()
        }
        None => cfg
            .benchmarks
            .iter()
            .map(|b| {
                let (status, res) = benchmark(&ws.path, &cfg, b, commit.id, None, timeout);
                (b.clone(), status, res)
            })
            .collect::<Vec<(Benchmark, ExitStatus, HashMap<String, BenchmarkResult<f64>>)>>(),
    };
    let bench_success = bench_results.iter().all(|x| x.1.success());

    Ok((
        Some(cfg),
        TastingResult {
            branch: branch,
            commit: commit.clone(),
            build: build_success,
            test: test_output.status.success(),
            bench: bench_success,
            results: Some(bench_results),
        },
    ))
}

fn test(workdir: &str, timeout: Option<u64>) -> Output {
    let mut cmd = match timeout {
        None => {
            let mut cmd = Command::new("cargo");
            cmd.arg("test").arg("--all");
            cmd
        }
        Some(timeout) => {
            let mut cmd = Command::new("timeout");
            cmd.arg("-k")
                .arg(&format!("{}s", timeout + 30))
                .arg(&format!("{}s", timeout))
                .arg("cargo")
                .arg("test")
                .arg("--all");
            cmd
        }
    };
    cmd.current_dir(workdir)
        .env("RUST_BACKTRACE", "1")
        .env("RUST_TEST_THREADS", "1")
        .output()
        .expect("Failed to execute 'cargo test'!")
}

fn update(workdir: &str) -> Output {
    Command::new("cargo")
        .current_dir(workdir)
        .arg("update")
        .output()
        .expect("Failed to execute 'cargo update'!")
}

fn version(workdir: &str) -> Output {
    Command::new("rustc")
        .current_dir(workdir)
        .arg("--version")
        .output()
        .expect("Failed to execute 'rustc --version'!")
}
<|endoftext|>


<commit_before>/*!
The types module provides a way of associating glob patterns on file names to
file types.
*/

use std::collections::HashMap;
use std::error::Error as StdError;
use std::fmt;
use std::path::Path;

use regex;

use gitignore::{Match, Pattern};
use glob::{self, MatchOptions};

const TYPE_EXTENSIONS: &'static [(&'static str, &'static [&'static str])] = &[
    ("asm", &["*.asm", "*.s", "*.S"]),
    ("awk", &["*.awk"]),
    ("c", &["*.c", "*.h", "*.H"]),
    ("cbor", &["*.cbor"]),
    ("clojure", &["*.clj", "*.cljs"]),
    ("cmake", &["CMakeLists.txt"]),
    ("coffeescript", &["*.coffee"]),
    ("cpp", &[
        "*.C", "*.cc", "*.cpp", "*.cxx",
        "*.h", "*.H", "*.hh", "*.hpp",
    ]),
    ("csharp", &["*.cs"]),
    ("css", &["*.css"]),
    ("cython", &["*.pyx"]),
    ("dart", &["*.dart"]),
    ("d", &["*.d"]),
    ("elisp", &["*.el"]),
    ("erlang", &["*.erl", "*.hrl"]),
    ("fortran", &[
        "*.f", "*.F", "*.f77", "*.F77", "*.pfo",
        "*.f90", "*.F90", "*.f95", "*.F95",
    ]),
    ("go", &["*.go"]),
    ("groovy", &["*.groovy"]),
    ("haskell", &["*.hs", "*.lhs"]),
    ("html", &["*.htm", "*.html"]),
    ("java", &["*.java"]),
    ("js", &["*.js"]),
    ("json", &["*.json"]),
    ("jsonl", &["*.jsonl"]),
    ("lisp", &["*.el", "*.jl", "*.lisp", "*.lsp", "*.sc", "*.scm"]),
    ("lua", &["*.lua"]),
    ("m4", &["*.ac", "*.m4"]),
    ("make", &["gnumakefile", "Gnumakefile", "makefile", "Makefile", "*.mk"]),
    ("markdown", &["*.md"]),
    ("matlab", &["*.m"]),
    ("mk", &["mkfile"]),
    ("ml", &["*.ml"]),
    ("objc", &["*.h", "*.m"]),
    ("objcpp", &["*.h", "*.mm"]),
    ("ocaml", &["*.ml", "*.mli", "*.mll", "*.mly"]),
    ("perl", &["*.perl", "*.pl", "*.PL", "*.plh", "*.plx", "*.pm"]),
    ("php", &["*.php", "*.php3", "*.php4", "*.php5", "*.phtml"]),
    ("py", &["*.py"]),
    ("readme", &["README*", "*README"]),
    ("rr", &["*.R"]),
    ("rst", &["*.rst"]),
    ("ruby", &["*.rb"]),
    ("rust", &["*.rs"]),
    ("scala", &["*.scala"]),
    ("sh", &["*.bash", "*.csh", "*.ksh", "*.sh", "*.tcsh"]),
    ("sql", &["*.sql"]),
    ("tex", &["*.tex", "*.cls", "*.sty"]),
    ("txt", &["*.txt"]),
    ("toml", &["*.toml", "Cargo.lock"]),
    ("vala", &["*.vala"]),
    ("vimscript", &["*.vim"]),
    ("xml", &["*.xml"]),
    ("yacc", &["*.y"]),
    ("yaml", &["*.yaml", "*.yml"]),
];

/// Describes all the possible failure conditions for building a file type
/// matcher.
#[derive(Debug)]
pub enum Error {
    /// We tried to select (or negate) a file type that is not defined.
    UnrecognizedFileType(String),
    /// A user specified file type definition could not be parsed.
    InvalidDefinition,
    /// There was an error building the matcher (probably a bad glob).
    Glob(glob::Error),
    /// There was an error compiling a glob as a regex.
    Regex(regex::Error),
}

impl StdError for Error {
    fn description(&self) -> &str {
        match *self {
            Error::UnrecognizedFileType(_) => "unrecognized file type",
            Error::InvalidDefinition => "invalid definition",
            Error::Glob(ref err) => err.description(),
            Error::Regex(ref err) => err.description(),
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::UnrecognizedFileType(ref ty) => {
                write!(f, "unrecognized file type: {}", ty)
            }
            Error::InvalidDefinition => {
                write!(f, "invalid definition (format is type:glob, e.g., \
                           html:*.html)")
            }
            Error::Glob(ref err) => err.fmt(f),
            Error::Regex(ref err) => err.fmt(f),
        }
    }
}

impl From<glob::Error> for Error {
    fn from(err: glob::Error) -> Error {
        Error::Glob(err)
    }
}

impl From<regex::Error> for Error {
    fn from(err: regex::Error) -> Error {
        Error::Regex(err)
    }
}

/// A single file type definition.
#[derive(Clone, Debug)]
pub struct FileTypeDef {
    name: String,
    pats: Vec<String>,
}

impl FileTypeDef {
    /// Return the name of this file type.
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Return the glob patterns used to recognize this file type.
    pub fn patterns(&self) -> &[String] {
        &self.pats
    }
}

/// Types is a file type matcher.
#[derive(Clone, Debug)]
pub struct Types {
    selected: Option<glob::SetYesNo>,
    negated: Option<glob::SetYesNo>,
    has_selected: bool,
    unmatched_pat: Pattern,
}

impl Types {
    /// Creates a new file type matcher from the given Gitignore matcher. If
    /// not Gitignore matcher is provided, then the file type matcher has no
    /// effect.
    ///
    /// If has_selected is true, then at least one file type was selected.
    /// Therefore, any non-matches should be ignored.
    fn new(
        selected: Option<glob::SetYesNo>,
        negated: Option<glob::SetYesNo>,
        has_selected: bool,
    ) -> Types {
        Types {
            selected: selected,
            negated: negated,
            has_selected: has_selected,
            unmatched_pat: Pattern {
                from: Path::new("<filetype>").to_path_buf(),
                original: "<N/A>".to_string(),
                pat: "<N/A>".to_string(),
                whitelist: false,
                only_dir: false,
            },
        }
    }

    /// Creates a new file type matcher that never matches.
    pub fn empty() -> Types {
        Types::new(None, None, false)
    }

    /// Returns a match for the given path against this file type matcher.
    ///
    /// The path is considered whitelisted if it matches a selected file type.
    /// The path is considered ignored if it matched a negated file type.
    /// If at least one file type is selected and path doesn't match, then
    /// the path is also considered ignored.
    pub fn matched<P: AsRef<Path>>(&self, path: P, is_dir: bool) -> Match {
        // If we don't have any matcher, then we can't do anything.
        if self.negated.is_none() && self.selected.is_none() {
            return Match::None;
        }
        // File types don't apply to directories.
        if is_dir {
            return Match::None;
        }
        let path = path.as_ref();
        let name = match path.file_name() {
            Some(name) => name.to_string_lossy(),
            None if self.has_selected => {
                return Match::Ignored(&self.unmatched_pat);
            }
            None => {
                return Match::None;
            }
        };
        if self.negated.as_ref().map(|s| s.is_match(&*name)).unwrap_or(false) {
            return Match::Ignored(&self.unmatched_pat);
        }
        if self.selected.as_ref().map(|s|s.is_match(&*name)).unwrap_or(false) {
            return Match::Whitelist(&self.unmatched_pat);
        }
        if self.has_selected {
            Match::Ignored(&self.unmatched_pat)
        } else {
            Match::None
        }
    }
}

/// TypesBuilder builds a type matcher from a set of file type definitions and
/// a set of file type selections.
pub struct TypesBuilder {
    types: HashMap<String, Vec<String>>,
    selected: Vec<String>,
    negated: Vec<String>,
}

impl TypesBuilder {
    /// Create a new builder for a file type matcher.
    pub fn new() -> TypesBuilder {
        TypesBuilder {
            types: HashMap::new(),
            selected: vec![],
            negated: vec![],
        }
    }

    /// Build the current set of file type definitions *and* selections into
    /// a file type matcher.
    pub fn build(&self) -> Result<Types, Error> {
        let opts = MatchOptions {
            require_literal_separator: true, ..MatchOptions::default()
        };
        let selected_globs =
            if self.selected.is_empty() {
                None
            } else {
                let mut bset = glob::SetBuilder::new();
                for name in &self.selected {
                    let globs = match self.types.get(name) {
                        Some(globs) => globs,
                        None => {
                            let msg = name.to_string();
                            return Err(Error::UnrecognizedFileType(msg));
                        }
                    };
                    for glob in globs {
                        try!(bset.add_with(glob, &opts));
                    }
                }
                Some(try!(bset.build_yesno()))
            };
        let negated_globs =
            if self.negated.is_empty() {
                None
            } else {
                let mut bset = glob::SetBuilder::new();
                for name in &self.negated {
                    let globs = match self.types.get(name) {
                        Some(globs) => globs,
                        None => {
                            let msg = name.to_string();
                            return Err(Error::UnrecognizedFileType(msg));
                        }
                    };
                    for glob in globs {
                        try!(bset.add_with(glob, &opts));
                    }
                }
                Some(try!(bset.build_yesno()))
            };
        Ok(Types::new(
            selected_globs, negated_globs, !self.selected.is_empty()))
    }

    /// Return the set of current file type definitions.
    pub fn definitions(&self) -> Vec<FileTypeDef> {
        let mut defs = vec![];
        for (ref name, ref pats) in &self.types {
            let mut pats = pats.to_vec();
            pats.sort();
            defs.push(FileTypeDef {
                name: name.to_string(),
                pats: pats,
            });
        }
        defs.sort_by(|def1, def2| def1.name().cmp(def2.name()));
        defs
    }

    /// Select the file type given by `name`.
    ///
    /// If `name` is `all`, then all file types are selected.
    pub fn select(&mut self, name: &str) -> &mut TypesBuilder {
        if name == "all" {
            for name in self.types.keys() {
                self.selected.push(name.to_string());
            }
        } else {
            self.selected.push(name.to_string());
        }
        self
    }

    /// Ignore the file type given by `name`.
    ///
    /// If `name` is `all`, then all file types are negated.
    pub fn negate(&mut self, name: &str) -> &mut TypesBuilder {
        if name == "all" {
            for name in self.types.keys() {
                self.negated.push(name.to_string());
            }
        } else {
            self.negated.push(name.to_string());
        }
        self
    }

    /// Clear any file type definitions for the type given.
    pub fn clear(&mut self, name: &str) -> &mut TypesBuilder {
        self.types.remove(name);
        self
    }

    /// Add a new file type definition. `name` can be arbitrary and `pat`
    /// should be a glob recognizing file paths belonging to the `name` type.
    pub fn add(&mut self, name: &str, pat: &str) -> &mut TypesBuilder {
        self.types.entry(name.to_string())
            .or_insert(vec![]).push(pat.to_string());
        self
    }

    /// Add a new file type definition specified in string form. The format
    /// is `name:glob`. Names may not include a colon.
    pub fn add_def(&mut self, def: &str) -> Result<(), Error> {
        let name: String = def.chars().take_while(|&c| c != ':').collect();
        let pat: String = def.chars().skip(name.chars().count() + 1).collect();
        if name.is_empty() || pat.is_empty() {
            return Err(Error::InvalidDefinition);
        }
        self.add(&name, &pat);
        Ok(())
    }

    /// Add a set of default file type definitions.
    pub fn add_defaults(&mut self) -> &mut TypesBuilder {
        for &(name, exts) in TYPE_EXTENSIONS {
            for ext in exts {
                self.add(name, ext);
            }
        }
        self
    }
}

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

    macro_rules! matched {
        ($name:ident, $types:expr, $sel:expr, $selnot:expr,
         $path:expr) => {
            matched!($name, $types, $sel, $selnot, $path, true);
        };
        (not, $name:ident, $types:expr, $sel:expr, $selnot:expr,
         $path:expr) => {
            matched!($name, $types, $sel, $selnot, $path, false);
        };
        ($name:ident, $types:expr, $sel:expr, $selnot:expr,
         $path:expr, $matched:expr) => {
            #[test]
            fn $name() {
                let mut btypes = TypesBuilder::new();
                for tydef in $types {
                    btypes.add_def(tydef).unwrap();
                }
                for sel in $sel {
                    btypes.select(sel);
                }
                for selnot in $selnot {
                    btypes.negate(selnot);
                }
                let types = btypes.build().unwrap();
                let mat = types.matched($path, false);
                assert_eq!($matched, !mat.is_ignored());
            }
        };
    }

    fn types() -> Vec<&'static str> {
        vec![
            "html:*.html",
            "html:*.htm",
            "rust:*.rs",
            "js:*.js",
        ]
    }

    matched!(match1, types(), vec!["rust"], vec![], "lib.rs");
    matched!(match2, types(), vec!["html"], vec![], "index.html");
    matched!(match3, types(), vec!["html"], vec![], "index.htm");
    matched!(match4, types(), vec!["html", "rust"], vec![], "main.rs");
    matched!(match5, types(), vec![], vec![], "index.html");
    matched!(match6, types(), vec![], vec!["rust"], "index.html");

    matched!(not, matchnot1, types(), vec!["rust"], vec![], "index.html");
    matched!(not, matchnot2, types(), vec![], vec!["rust"], "main.rs");
}
<commit_msg>Add VB files to default type list<commit_after>/*!
The types module provides a way of associating glob patterns on file names to
file types.
*/

use std::collections::HashMap;
use std::error::Error as StdError;
use std::fmt;
use std::path::Path;

use regex;

use gitignore::{Match, Pattern};
use glob::{self, MatchOptions};

const TYPE_EXTENSIONS: &'static [(&'static str, &'static [&'static str])] = &[
    ("asm", &["*.asm", "*.s", "*.S"]),
    ("awk", &["*.awk"]),
    ("c", &["*.c", "*.h", "*.H"]),
    ("cbor", &["*.cbor"]),
    ("clojure", &["*.clj", "*.cljs"]),
    ("cmake", &["CMakeLists.txt"]),
    ("coffeescript", &["*.coffee"]),
    ("cpp", &[
        "*.C", "*.cc", "*.cpp", "*.cxx",
        "*.h", "*.H", "*.hh", "*.hpp",
    ]),
    ("csharp", &["*.cs"]),
    ("css", &["*.css"]),
    ("cython", &["*.pyx"]),
    ("dart", &["*.dart"]),
    ("d", &["*.d"]),
    ("elisp", &["*.el"]),
    ("erlang", &["*.erl", "*.hrl"]),
    ("fortran", &[
        "*.f", "*.F", "*.f77", "*.F77", "*.pfo",
        "*.f90", "*.F90", "*.f95", "*.F95",
    ]),
    ("go", &["*.go"]),
    ("groovy", &["*.groovy"]),
    ("haskell", &["*.hs", "*.lhs"]),
    ("html", &["*.htm", "*.html"]),
    ("java", &["*.java"]),
    ("js", &["*.js"]),
    ("json", &["*.json"]),
    ("jsonl", &["*.jsonl"]),
    ("lisp", &["*.el", "*.jl", "*.lisp", "*.lsp", "*.sc", "*.scm"]),
    ("lua", &["*.lua"]),
    ("m4", &["*.ac", "*.m4"]),
    ("make", &["gnumakefile", "Gnumakefile", "makefile", "Makefile", "*.mk"]),
    ("markdown", &["*.md"]),
    ("matlab", &["*.m"]),
    ("mk", &["mkfile"]),
    ("ml", &["*.ml"]),
    ("objc", &["*.h", "*.m"]),
    ("objcpp", &["*.h", "*.mm"]),
    ("ocaml", &["*.ml", "*.mli", "*.mll", "*.mly"]),
    ("perl", &["*.perl", "*.pl", "*.PL", "*.plh", "*.plx", "*.pm"]),
    ("php", &["*.php", "*.php3", "*.php4", "*.php5", "*.phtml"]),
    ("py", &["*.py"]),
    ("readme", &["README*", "*README"]),
    ("rr", &["*.R"]),
    ("rst", &["*.rst"]),
    ("ruby", &["*.rb"]),
    ("rust", &["*.rs"]),
    ("scala", &["*.scala"]),
    ("sh", &["*.bash", "*.csh", "*.ksh", "*.sh", "*.tcsh"]),
    ("sql", &["*.sql"]),
    ("tex", &["*.tex", "*.cls", "*.sty"]),
    ("txt", &["*.txt"]),
    ("toml", &["*.toml", "Cargo.lock"]),
    ("vala", &["*.vala"]),
    ("vb", &["*.vb"]),
    ("vimscript", &["*.vim"]),
    ("xml", &["*.xml"]),
    ("yacc", &["*.y"]),
    ("yaml", &["*.yaml", "*.yml"]),
];

/// Describes all the possible failure conditions for building a file type
/// matcher.
#[derive(Debug)]
pub enum Error {
    /// We tried to select (or negate) a file type that is not defined.
    UnrecognizedFileType(String),
    /// A user specified file type definition could not be parsed.
    InvalidDefinition,
    /// There was an error building the matcher (probably a bad glob).
    Glob(glob::Error),
    /// There was an error compiling a glob as a regex.
    Regex(regex::Error),
}

impl StdError for Error {
    fn description(&self) -> &str {
        match *self {
            Error::UnrecognizedFileType(_) => "unrecognized file type",
            Error::InvalidDefinition => "invalid definition",
            Error::Glob(ref err) => err.description(),
            Error::Regex(ref err) => err.description(),
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::UnrecognizedFileType(ref ty) => {
                write!(f, "unrecognized file type: {}", ty)
            }
            Error::InvalidDefinition => {
                write!(f, "invalid definition (format is type:glob, e.g., \
                           html:*.html)")
            }
            Error::Glob(ref err) => err.fmt(f),
            Error::Regex(ref err) => err.fmt(f),
        }
    }
}

impl From<glob::Error> for Error {
    fn from(err: glob::Error) -> Error {
        Error::Glob(err)
    }
}

impl From<regex::Error> for Error {
    fn from(err: regex::Error) -> Error {
        Error::Regex(err)
    }
}

/// A single file type definition.
#[derive(Clone, Debug)]
pub struct FileTypeDef {
    name: String,
    pats: Vec<String>,
}

impl FileTypeDef {
    /// Return the name of this file type.
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Return the glob patterns used to recognize this file type.
    pub fn patterns(&self) -> &[String] {
        &self.pats
    }
}

/// Types is a file type matcher.
#[derive(Clone, Debug)]
pub struct Types {
    selected: Option<glob::SetYesNo>,
    negated: Option<glob::SetYesNo>,
    has_selected: bool,
    unmatched_pat: Pattern,
}

impl Types {
    /// Creates a new file type matcher from the given Gitignore matcher. If
    /// not Gitignore matcher is provided, then the file type matcher has no
    /// effect.
    ///
    /// If has_selected is true, then at least one file type was selected.
    /// Therefore, any non-matches should be ignored.
    fn new(
        selected: Option<glob::SetYesNo>,
        negated: Option<glob::SetYesNo>,
        has_selected: bool,
    ) -> Types {
        Types {
            selected: selected,
            negated: negated,
            has_selected: has_selected,
            unmatched_pat: Pattern {
                from: Path::new("<filetype>").to_path_buf(),
                original: "<N/A>".to_string(),
                pat: "<N/A>".to_string(),
                whitelist: false,
                only_dir: false,
            },
        }
    }

    /// Creates a new file type matcher that never matches.
    pub fn empty() -> Types {
        Types::new(None, None, false)
    }

    /// Returns a match for the given path against this file type matcher.
    ///
    /// The path is considered whitelisted if it matches a selected file type.
    /// The path is considered ignored if it matched a negated file type.
    /// If at least one file type is selected and path doesn't match, then
    /// the path is also considered ignored.
    pub fn matched<P: AsRef<Path>>(&self, path: P, is_dir: bool) -> Match {
        // If we don't have any matcher, then we can't do anything.
        if self.negated.is_none() && self.selected.is_none() {
            return Match::None;
        }
        // File types don't apply to directories.
        if is_dir {
            return Match::None;
        }
        let path = path.as_ref();
        let name = match path.file_name() {
            Some(name) => name.to_string_lossy(),
            None if self.has_selected => {
                return Match::Ignored(&self.unmatched_pat);
            }
            None => {
                return Match::None;
            }
        };
        if self.negated.as_ref().map(|s| s.is_match(&*name)).unwrap_or(false) {
            return Match::Ignored(&self.unmatched_pat);
        }
        if self.selected.as_ref().map(|s|s.is_match(&*name)).unwrap_or(false) {
            return Match::Whitelist(&self.unmatched_pat);
        }
        if self.has_selected {
            Match::Ignored(&self.unmatched_pat)
        } else {
            Match::None
        }
    }
}

/// TypesBuilder builds a type matcher from a set of file type definitions and
/// a set of file type selections.
pub struct TypesBuilder {
    types: HashMap<String, Vec<String>>,
    selected: Vec<String>,
    negated: Vec<String>,
}

impl TypesBuilder {
    /// Create a new builder for a file type matcher.
    pub fn new() -> TypesBuilder {
        TypesBuilder {
            types: HashMap::new(),
            selected: vec![],
            negated: vec![],
        }
    }

    /// Build the current set of file type definitions *and* selections into
    /// a file type matcher.
    pub fn build(&self) -> Result<Types, Error> {
        let opts = MatchOptions {
            require_literal_separator: true, ..MatchOptions::default()
        };
        let selected_globs =
            if self.selected.is_empty() {
                None
            } else {
                let mut bset = glob::SetBuilder::new();
                for name in &self.selected {
                    let globs = match self.types.get(name) {
                        Some(globs) => globs,
                        None => {
                            let msg = name.to_string();
                            return Err(Error::UnrecognizedFileType(msg));
                        }
                    };
                    for glob in globs {
                        try!(bset.add_with(glob, &opts));
                    }
                }
                Some(try!(bset.build_yesno()))
            };
        let negated_globs =
            if self.negated.is_empty() {
                None
            } else {
                let mut bset = glob::SetBuilder::new();
                for name in &self.negated {
                    let globs = match self.types.get(name) {
                        Some(globs) => globs,
                        None => {
                            let msg = name.to_string();
                            return Err(Error::UnrecognizedFileType(msg));
                        }
                    };
                    for glob in globs {
                        try!(bset.add_with(glob, &opts));
                    }
                }
                Some(try!(bset.build_yesno()))
            };
        Ok(Types::new(
            selected_globs, negated_globs, !self.selected.is_empty()))
    }

    /// Return the set of current file type definitions.
    pub fn definitions(&self) -> Vec<FileTypeDef> {
        let mut defs = vec![];
        for (ref name, ref pats) in &self.types {
            let mut pats = pats.to_vec();
            pats.sort();
            defs.push(FileTypeDef {
                name: name.to_string(),
                pats: pats,
            });
        }
        defs.sort_by(|def1, def2| def1.name().cmp(def2.name()));
        defs
    }

    /// Select the file type given by `name`.
    ///
    /// If `name` is `all`, then all file types are selected.
    pub fn select(&mut self, name: &str) -> &mut TypesBuilder {
        if name == "all" {
            for name in self.types.keys() {
                self.selected.push(name.to_string());
            }
        } else {
            self.selected.push(name.to_string());
        }
        self
    }

    /// Ignore the file type given by `name`.
    ///
    /// If `name` is `all`, then all file types are negated.
    pub fn negate(&mut self, name: &str) -> &mut TypesBuilder {
        if name == "all" {
            for name in self.types.keys() {
                self.negated.push(name.to_string());
            }
        } else {
            self.negated.push(name.to_string());
        }
        self
    }

    /// Clear any file type definitions for the type given.
    pub fn clear(&mut self, name: &str) -> &mut TypesBuilder {
        self.types.remove(name);
        self
    }

    /// Add a new file type definition. `name` can be arbitrary and `pat`
    /// should be a glob recognizing file paths belonging to the `name` type.
    pub fn add(&mut self, name: &str, pat: &str) -> &mut TypesBuilder {
        self.types.entry(name.to_string())
            .or_insert(vec![]).push(pat.to_string());
        self
    }

    /// Add a new file type definition specified in string form. The format
    /// is `name:glob`. Names may not include a colon.
    pub fn add_def(&mut self, def: &str) -> Result<(), Error> {
        let name: String = def.chars().take_while(|&c| c != ':').collect();
        let pat: String = def.chars().skip(name.chars().count() + 1).collect();
        if name.is_empty() || pat.is_empty() {
            return Err(Error::InvalidDefinition);
        }
        self.add(&name, &pat);
        Ok(())
    }

    /// Add a set of default file type definitions.
    pub fn add_defaults(&mut self) -> &mut TypesBuilder {
        for &(name, exts) in TYPE_EXTENSIONS {
            for ext in exts {
                self.add(name, ext);
            }
        }
        self
    }
}

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

    macro_rules! matched {
        ($name:ident, $types:expr, $sel:expr, $selnot:expr,
         $path:expr) => {
            matched!($name, $types, $sel, $selnot, $path, true);
        };
        (not, $name:ident, $types:expr, $sel:expr, $selnot:expr,
         $path:expr) => {
            matched!($name, $types, $sel, $selnot, $path, false);
        };
        ($name:ident, $types:expr, $sel:expr, $selnot:expr,
         $path:expr, $matched:expr) => {
            #[test]
            fn $name() {
                let mut btypes = TypesBuilder::new();
                for tydef in $types {
                    btypes.add_def(tydef).unwrap();
                }
                for sel in $sel {
                    btypes.select(sel);
                }
                for selnot in $selnot {
                    btypes.negate(selnot);
                }
                let types = btypes.build().unwrap();
                let mat = types.matched($path, false);
                assert_eq!($matched, !mat.is_ignored());
            }
        };
    }

    fn types() -> Vec<&'static str> {
        vec![
            "html:*.html",
            "html:*.htm",
            "rust:*.rs",
            "js:*.js",
        ]
    }

    matched!(match1, types(), vec!["rust"], vec![], "lib.rs");
    matched!(match2, types(), vec!["html"], vec![], "index.html");
    matched!(match3, types(), vec!["html"], vec![], "index.htm");
    matched!(match4, types(), vec!["html", "rust"], vec![], "main.rs");
    matched!(match5, types(), vec![], vec![], "index.html");
    matched!(match6, types(), vec![], vec!["rust"], "index.html");

    matched!(not, matchnot1, types(), vec!["rust"], vec![], "index.html");
    matched!(not, matchnot2, types(), vec![], vec!["rust"], "main.rs");
}
<|endoftext|>


<commit_before>//! This crate implements the standard attribute dictionary.
//!
//! This dictionary is a singleton containing all information about the
//! DICOM attributes specified in the standard according to DICOM PS3.6 2019c,
//! and it will be used by default in most other abstractions available.
//!
//! When not using private tags, this dictionary should suffice.

mod entries;

use std::collections::{HashMap, HashSet};
use std::fmt;
use std::fmt::{Display, Formatter};
use dicom_core::dictionary::{DataDictionary, DictionaryEntryRef, TagRange::*};
use dicom_core::header::{Tag, VR};
use lazy_static::lazy_static;
use crate::entries::ENTRIES;

lazy_static! {
    static ref DICT: StandardDictionaryRegistry = {
        init_dictionary()
    };
}

/// Retrieve a singleton instance of the standard dictionary registry.
pub fn registry() -> &'static StandardDictionaryRegistry {
    &DICT
}

/// The data struct containing the standard dictionary.
#[derive(Debug)]
pub struct StandardDictionaryRegistry {
    /// mapping: name → tag
    by_name: HashMap<&'static str, &'static DictionaryEntryRef<'static>>,
    /// mapping: tag → name
    by_tag: HashMap<Tag, &'static DictionaryEntryRef<'static>>,
    /// repeating elements of the form (ggxx, eeee). The `xx` portion is zeroed.
    repeating_ggxx: HashSet<Tag>,
    /// repeating elements of the form (gggg, eexx). The `xx` portion is zeroed.
    repeating_eexx: HashSet<Tag>,
}

impl StandardDictionaryRegistry {
    fn new() -> StandardDictionaryRegistry {
        StandardDictionaryRegistry {
            by_name: HashMap::new(),
            by_tag: HashMap::new(),
            repeating_ggxx: HashSet::new(),
            repeating_eexx: HashSet::new(),
        }
    }

    /// record the given dictionary entry reference
    fn index(&mut self, entry: &'static DictionaryEntryRef<'static>) -> &mut Self {
        self.by_name.insert(entry.alias, entry);
        self.by_tag.insert(entry.tag.inner(), entry);
        match entry.tag {
            Group100(tag) => {
                self.repeating_ggxx.insert(tag);
            },
            Element100(tag) => {
                self.repeating_eexx.insert(tag);
            },
            _ => {}
        }
        self
    }
}

/// A data dictionary which consults the library's global DICOM attribute registry.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct StandardDataDictionary;

impl StandardDataDictionary {
    fn indexed_tag(tag: Tag) -> Option<&'static DictionaryEntryRef<'static>> {
        let r = registry();

        r.by_tag.get(&tag)
            .or_else(|| {
                let group_trimmed = Tag(tag.0 & 0xFF00, tag.1);

                if r.repeating_ggxx.contains(&group_trimmed) {
                    eprintln!("Found ranged group {:x} for tag {:x?}", group_trimmed.0, tag);
                    r.by_tag.get(&group_trimmed)
                } else {
                    let elem_trimmed = Tag(tag.0, tag.1 & 0xFF00);
                    if r.repeating_eexx.contains(&elem_trimmed) {
                        r.by_tag.get(&elem_trimmed)
                    } else {
                        None
                    }
                }
            }).cloned()
    }
}

impl DataDictionary for StandardDataDictionary {
    type Entry = DictionaryEntryRef<'static>;

    fn by_name(&self, name: &str) -> Option<&Self::Entry> {
        registry().by_name.get(name).cloned()
    }

    fn by_tag(&self, tag: Tag) -> Option<&Self::Entry> {
        StandardDataDictionary::indexed_tag(tag)
    }
}

impl<'a> DataDictionary for &'a StandardDataDictionary {
    type Entry = DictionaryEntryRef<'static>;

    fn by_name(&self, name: &str) -> Option<&'static DictionaryEntryRef<'static>> {
        registry().by_name.get(name).cloned()
    }

    fn by_tag(&self, tag: Tag) -> Option<&'static DictionaryEntryRef<'static>> {
        StandardDataDictionary::indexed_tag(tag)
    }
}

impl Display for StandardDataDictionary {
    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
        f.write_str("Standard DICOM Data Dictionary")
    }
}

fn init_dictionary() -> StandardDictionaryRegistry {
    let mut d = StandardDictionaryRegistry::new();
    for entry in ENTRIES {
        d.index(&entry);
    }
    for entry in META_ENTRIES {
        d.index(&entry);
    }
    d
}

// meta information entries
type E<'a> = DictionaryEntryRef<'a>;
const META_ENTRIES: &'static [E<'static>] = &[
    E {
        tag: Single(Tag(0x0002, 0x0000)),
        alias: "FileMetaInformationGroupLength",
        vr: VR::UL,
    },
    E {
        tag: Single(Tag(0x0002, 0x0001)),
        alias: "FileMetaInformationVersion",
        vr: VR::OB,
    },
    E {
        tag: Single(Tag(0x0002, 0x0002)),
        alias: "MediaStorageSOPClassUID",
        vr: VR::UI,
    },
    E {
        tag: Single(Tag(0x0002, 0x0003)),
        alias: "MediaStorageSOPInstanceUID",
        vr: VR::UI,
    },
    E {
        tag: Single(Tag(0x0002, 0x0010)),
        alias: "TransferSyntaxUID",
        vr: VR::UI,
    },
    E {
        tag: Single(Tag(0x0002, 0x0012)),
        alias: "ImplementationClassUID",
        vr: VR::UI,
    },
    E {
        tag: Single(Tag(0x0002, 0x0013)),
        alias: "ImplentationVersionName",
        vr: VR::SH,
    },
    E {
        tag: Single(Tag(0x0002, 0x0016)),
        alias: "SourceApplicationEntityTitle",
        vr: VR::AE,
    },
    E {
        tag: Single(Tag(0x0002, 0x0017)),
        alias: "SendingApplicationEntityTitle",
        vr: VR::AE,
    },
    E {
        tag: Single(Tag(0x0002, 0x0018)),
        alias: "ReceivingApplicationEntityTitle",
        vr: VR::AE,
    },
    E {
        tag: Single(Tag(0x0002, 0x0100)),
        alias: "PrivateInformationCreatorUID",
        vr: VR::UI,
    },
    E {
        tag: Single(Tag(0x0002, 0x0102)),
        alias: "PrivateInformation",
        vr: VR::OB,
    },
];

#[cfg(test)]
mod tests {
    use super::StandardDataDictionary;
    use dicom_core::dictionary::{DataDictionary, DictionaryEntryRef, TagRange::*};
    use dicom_core::header::{Tag, VR};

    // tests for just a few attributes to make sure that the entries
    // were well installed into the crate
    #[test]
    fn smoke_test() {
        let dict = StandardDataDictionary::default();

        assert_eq!(
            dict.by_name("PatientName"),
            Some(&DictionaryEntryRef {
                tag: Single(Tag(0x0010, 0x0010)),
                alias: "PatientName",
                vr: VR::PN,
            })
        );

        assert_eq!(
            dict.by_name("Modality"),
            Some(&DictionaryEntryRef {
                tag: Single(Tag(0x0008, 0x0060)),
                alias: "Modality",
                vr: VR::CS,
            })
        );

        let pixel_data = dict.by_tag(Tag(0x7FE0, 0x0010))
            .expect("Pixel Data attribute should exist");
        eprintln!("{:X?}", pixel_data.tag);
        assert_eq!(pixel_data.tag, Single(Tag(0x7FE0, 0x0010)));
        assert_eq!(pixel_data.alias, "PixelData");
        assert!(pixel_data.vr == VR::OB || pixel_data.vr == VR::OW);

        let overlay_data = dict.by_tag(Tag(0x6000, 0x3000))
            .expect("Overlay Data attribute should exist");
        assert_eq!(overlay_data.tag, Group100(Tag(0x6000, 0x3000)));
        assert_eq!(overlay_data.alias, "OverlayData");
        assert!(overlay_data.vr == VR::OB || overlay_data.vr == VR::OW);

        // repeated overlay data
        let overlay_data = dict.by_tag(Tag(0x60EE, 0x3000))
            .expect("Repeated Overlay Data attribute should exist");
        assert_eq!(overlay_data.tag, Group100(Tag(0x6000, 0x3000)));
        assert_eq!(overlay_data.alias, "OverlayData");
        assert!(overlay_data.vr == VR::OB || overlay_data.vr == VR::OW);
    }
}
<commit_msg>Improve dictionary_std warm-up<commit_after>//! This crate implements the standard attribute dictionary.
//!
//! This dictionary is a singleton containing all information about the
//! DICOM attributes specified in the standard according to DICOM PS3.6 2019c,
//! and it will be used by default in most other abstractions available.
//!
//! When not using private tags, this dictionary should suffice.

mod entries;

use std::collections::{HashMap, HashSet};
use std::fmt;
use std::fmt::{Display, Formatter};
use dicom_core::dictionary::{DataDictionary, DictionaryEntryRef, TagRange::*};
use dicom_core::header::{Tag, VR};
use lazy_static::lazy_static;
use crate::entries::ENTRIES;

lazy_static! {
    static ref DICT: StandardDictionaryRegistry = {
        init_dictionary()
    };
}

/// Retrieve a singleton instance of the standard dictionary registry.
pub fn registry() -> &'static StandardDictionaryRegistry {
    &DICT
}

/// The data struct containing the standard dictionary.
#[derive(Debug)]
pub struct StandardDictionaryRegistry {
    /// mapping: name → tag
    by_name: HashMap<&'static str, &'static DictionaryEntryRef<'static>>,
    /// mapping: tag → name
    by_tag: HashMap<Tag, &'static DictionaryEntryRef<'static>>,
    /// repeating elements of the form (ggxx, eeee). The `xx` portion is zeroed.
    repeating_ggxx: HashSet<Tag>,
    /// repeating elements of the form (gggg, eexx). The `xx` portion is zeroed.
    repeating_eexx: HashSet<Tag>,
}

impl StandardDictionaryRegistry {
    fn new() -> StandardDictionaryRegistry {
        StandardDictionaryRegistry {
            by_name: HashMap::with_capacity(5000),
            by_tag: HashMap::with_capacity(5000),
            repeating_ggxx: HashSet::with_capacity(75),
            repeating_eexx: HashSet::new(),
        }
    }

    /// record the given dictionary entry reference
    fn index(&mut self, entry: &'static DictionaryEntryRef<'static>) -> &mut Self {
        self.by_name.insert(entry.alias, entry);
        self.by_tag.insert(entry.tag.inner(), entry);
        match entry.tag {
            Group100(tag) => {
                self.repeating_ggxx.insert(tag);
            },
            Element100(tag) => {
                self.repeating_eexx.insert(tag);
            },
            _ => {}
        }
        self
    }
}

/// A data dictionary which consults the library's global DICOM attribute registry.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct StandardDataDictionary;

impl StandardDataDictionary {
    fn indexed_tag(tag: Tag) -> Option<&'static DictionaryEntryRef<'static>> {
        let r = registry();

        r.by_tag.get(&tag)
            .or_else(|| {
                let group_trimmed = Tag(tag.0 & 0xFF00, tag.1);

                if r.repeating_ggxx.contains(&group_trimmed) {
                    r.by_tag.get(&group_trimmed)
                } else {
                    let elem_trimmed = Tag(tag.0, tag.1 & 0xFF00);
                    if r.repeating_eexx.contains(&elem_trimmed) {
                        r.by_tag.get(&elem_trimmed)
                    } else {
                        None
                    }
                }
            }).cloned()
    }
}

impl DataDictionary for StandardDataDictionary {
    type Entry = DictionaryEntryRef<'static>;

    fn by_name(&self, name: &str) -> Option<&Self::Entry> {
        registry().by_name.get(name).cloned()
    }

    fn by_tag(&self, tag: Tag) -> Option<&Self::Entry> {
        StandardDataDictionary::indexed_tag(tag)
    }
}

impl<'a> DataDictionary for &'a StandardDataDictionary {
    type Entry = DictionaryEntryRef<'static>;

    fn by_name(&self, name: &str) -> Option<&'static DictionaryEntryRef<'static>> {
        registry().by_name.get(name).cloned()
    }

    fn by_tag(&self, tag: Tag) -> Option<&'static DictionaryEntryRef<'static>> {
        StandardDataDictionary::indexed_tag(tag)
    }
}

impl Display for StandardDataDictionary {
    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
        f.write_str("Standard DICOM Data Dictionary")
    }
}

fn init_dictionary() -> StandardDictionaryRegistry {
    let mut d = StandardDictionaryRegistry::new();
    for entry in ENTRIES {
        d.index(&entry);
    }
    for entry in META_ENTRIES {
        d.index(&entry);
    }
    d
}

// meta information entries
type E<'a> = DictionaryEntryRef<'a>;
const META_ENTRIES: &'static [E<'static>] = &[
    E {
        tag: Single(Tag(0x0002, 0x0000)),
        alias: "FileMetaInformationGroupLength",
        vr: VR::UL,
    },
    E {
        tag: Single(Tag(0x0002, 0x0001)),
        alias: "FileMetaInformationVersion",
        vr: VR::OB,
    },
    E {
        tag: Single(Tag(0x0002, 0x0002)),
        alias: "MediaStorageSOPClassUID",
        vr: VR::UI,
    },
    E {
        tag: Single(Tag(0x0002, 0x0003)),
        alias: "MediaStorageSOPInstanceUID",
        vr: VR::UI,
    },
    E {
        tag: Single(Tag(0x0002, 0x0010)),
        alias: "TransferSyntaxUID",
        vr: VR::UI,
    },
    E {
        tag: Single(Tag(0x0002, 0x0012)),
        alias: "ImplementationClassUID",
        vr: VR::UI,
    },
    E {
        tag: Single(Tag(0x0002, 0x0013)),
        alias: "ImplentationVersionName",
        vr: VR::SH,
    },
    E {
        tag: Single(Tag(0x0002, 0x0016)),
        alias: "SourceApplicationEntityTitle",
        vr: VR::AE,
    },
    E {
        tag: Single(Tag(0x0002, 0x0017)),
        alias: "SendingApplicationEntityTitle",
        vr: VR::AE,
    },
    E {
        tag: Single(Tag(0x0002, 0x0018)),
        alias: "ReceivingApplicationEntityTitle",
        vr: VR::AE,
    },
    E {
        tag: Single(Tag(0x0002, 0x0100)),
        alias: "PrivateInformationCreatorUID",
        vr: VR::UI,
    },
    E {
        tag: Single(Tag(0x0002, 0x0102)),
        alias: "PrivateInformation",
        vr: VR::OB,
    },
];

#[cfg(test)]
mod tests {
    use super::StandardDataDictionary;
    use dicom_core::dictionary::{DataDictionary, DictionaryEntryRef, TagRange::*};
    use dicom_core::header::{Tag, VR};

    // tests for just a few attributes to make sure that the entries
    // were well installed into the crate
    #[test]
    fn smoke_test() {
        let dict = StandardDataDictionary::default();

        assert_eq!(
            dict.by_name("PatientName"),
            Some(&DictionaryEntryRef {
                tag: Single(Tag(0x0010, 0x0010)),
                alias: "PatientName",
                vr: VR::PN,
            })
        );

        assert_eq!(
            dict.by_name("Modality"),
            Some(&DictionaryEntryRef {
                tag: Single(Tag(0x0008, 0x0060)),
                alias: "Modality",
                vr: VR::CS,
            })
        );

        let pixel_data = dict.by_tag(Tag(0x7FE0, 0x0010))
            .expect("Pixel Data attribute should exist");
        eprintln!("{:X?}", pixel_data.tag);
        assert_eq!(pixel_data.tag, Single(Tag(0x7FE0, 0x0010)));
        assert_eq!(pixel_data.alias, "PixelData");
        assert!(pixel_data.vr == VR::OB || pixel_data.vr == VR::OW);

        let overlay_data = dict.by_tag(Tag(0x6000, 0x3000))
            .expect("Overlay Data attribute should exist");
        assert_eq!(overlay_data.tag, Group100(Tag(0x6000, 0x3000)));
        assert_eq!(overlay_data.alias, "OverlayData");
        assert!(overlay_data.vr == VR::OB || overlay_data.vr == VR::OW);

        // repeated overlay data
        let overlay_data = dict.by_tag(Tag(0x60EE, 0x3000))
            .expect("Repeated Overlay Data attribute should exist");
        assert_eq!(overlay_data.tag, Group100(Tag(0x6000, 0x3000)));
        assert_eq!(overlay_data.alias, "OverlayData");
        assert!(overlay_data.vr == VR::OB || overlay_data.vr == VR::OW);
    }
}
<|endoftext|>


<commit_before>use std::collections::hash_map::HashMap;
use std::collections::hash_set::HashSet;
use std::fmt::{Display, Formatter, Error};
use std::string::String;
use std::vec::Vec;

use alloc_box::AllocBox;
use super::binding::UniqueBinding;
use super::js_var::{JsVar, JsKey, JsType, JsPtrEnum};

macro_rules! ptr_type_mismatch {
    ($tag:expr, $ptr:expr) => {
        panic!("Pointer type mismatch: expected {:?} but received {:?}", $tag, $ptr);
    }
}

pub type JsProto = Option<Box<JsObjStruct>>;

#[derive(Clone, Debug)]
pub struct JsObjStruct {
    pub proto: JsProto,
    pub name: String,
    pub dict: HashMap<JsKey, JsVar>,
}

impl JsObjStruct {
    #[allow(unused_variables)]
    pub fn new(proto: JsProto, name: &str, kv_tuples: Vec<(JsKey, JsVar, Option<JsPtrEnum>)>,
               allocator: &mut AllocBox) -> JsObjStruct {
        let proto_vec : Vec<_> = match proto {
            Some(ref obj) => obj.clone().dict.into_iter().map(|(key, var)| {
                let ptr = allocator.find_id(&var.unique).map(|ptr| ptr.borrow().clone());
                (key, JsVar::new(var.t), ptr)
            }).collect(),
            None => Vec::new(),
        };

        JsObjStruct {
            proto: proto,
            name: String::from(name),
            dict: proto_vec.into_iter().chain(kv_tuples.into_iter()).map(|(k, v, ptr)| {
                match v.t {
                    JsType::JsPtr(ref tag) => match ptr {
                        Some(ptr) => {
                            if tag.eq_ptr_type(&ptr) {
                                allocator.alloc(v.unique.clone(), ptr)
                                    .expect("Unable to allocate!"); // TODO better error handling
                            } else {
                                ptr_type_mismatch!(tag, ptr);
                            }
                        },
                        None => ptr_type_mismatch!(tag, None::<JsPtrEnum>),
                    },
                    _ => match ptr {
                        Some(ptr) => ptr_type_mismatch!(None::<JsPtrEnum>, ptr),
                        None => {},
                    },
                }
                (k, v)
            }).collect()
        }
    }

    pub fn add_key(&mut self, obj_binding: &UniqueBinding, k: JsKey, v: JsVar,
                   ptr: Option<JsPtrEnum>, allocator: &mut AllocBox) {
        if let Some(var) = self.dict.get(&k) {
            match var.t {
                JsType::JsPtr(_) => { allocator.condemn(var.unique.clone()).expect("Unable to whiten!") },
                _ => {}
            }
        }
        if let Some(ptr) = ptr {
            allocator.alloc(v.unique.clone(), ptr).expect("Unable to allocate!"); // TODO better error handling
        }

        match allocator.find_id(obj_binding) {
            Some(ref ptr) => match &mut *(ptr.borrow_mut()) {
                &mut JsPtrEnum::JsObj(ref mut obj) => obj.dict.insert(k.clone(), v.clone()),
                _ => panic!("Binding does not belong to an object!"),
            },
            None => panic!("No pointer with matching binding found!"),
        };

        self.dict.insert(k, v);
    }

    pub fn remove_key(&mut self, obj_binding: &UniqueBinding, k: &JsKey, allocator: &mut AllocBox)
        -> Option<(JsVar, Option<JsPtrEnum>)>{
        let var_opt = match &mut *(allocator.find_id(obj_binding).expect("No pointer with matching binding found!").borrow_mut()) {
            &mut JsPtrEnum::JsObj(ref mut obj) => obj.dict.remove(k),
            _ => panic!("Binding does not belong to an object!"),
        };

        if let Some(var) = var_opt {
            let ptr = allocator.find_id(&var.unique).map(|s| s.borrow().clone());

            if ptr.is_some() {
                allocator.condemn(var.unique.clone()).expect("Unable to whiten!");
            }

            let _ = self.dict.remove(k);
            Some((var, ptr))
        } else {
            None
        }
    }

    pub fn get_children(&self) -> HashSet<UniqueBinding> {
        let mut bindings = HashSet::new();
        for v in self.dict.values() {
            match v.t {
                JsType::JsPtr(_) => { bindings.insert(v.unique.clone()); },
                _ => (),
            }
        }
        bindings
    }
}

impl Display for JsObjStruct {
    fn fmt(&self, mut fmt: &mut Formatter) -> Result<(), Error> {
        try!(write!(fmt, "{{ "));

        for (i, (ref key, ref val)) in self.dict.iter().enumerate() {
            if i != 0 {
                try!(write!(fmt, ", "));
            }

            try!(write!(fmt, "{}: {}", key, val));
        }

        write!(fmt, " }}")
    }
}
<commit_msg>Don't allocate object field pointers when adding them to a pre-allocated object (the interpreter allocates them first)<commit_after>use std::collections::hash_map::HashMap;
use std::collections::hash_set::HashSet;
use std::fmt::{Display, Formatter, Error};
use std::string::String;
use std::vec::Vec;

use alloc_box::AllocBox;
use super::binding::UniqueBinding;
use super::js_var::{JsVar, JsKey, JsType, JsPtrEnum};

macro_rules! ptr_type_mismatch {
    ($tag:expr, $ptr:expr) => {
        panic!("Pointer type mismatch: expected {:?} but received {:?}", $tag, $ptr);
    }
}

pub type JsProto = Option<Box<JsObjStruct>>;

#[derive(Clone, Debug)]
pub struct JsObjStruct {
    pub proto: JsProto,
    pub name: String,
    pub dict: HashMap<JsKey, JsVar>,
}

impl JsObjStruct {
    #[allow(unused_variables)]
    pub fn new(proto: JsProto, name: &str, kv_tuples: Vec<(JsKey, JsVar, Option<JsPtrEnum>)>,
               allocator: &mut AllocBox) -> JsObjStruct {
        let proto_vec : Vec<_> = match proto {
            Some(ref obj) => obj.clone().dict.into_iter().map(|(key, var)| {
                let ptr = allocator.find_id(&var.unique).map(|ptr| ptr.borrow().clone());
                (key, JsVar::new(var.t), ptr)
            }).collect(),
            None => Vec::new(),
        };

        JsObjStruct {
            proto: proto,
            name: String::from(name),
            dict: proto_vec.into_iter().chain(kv_tuples.into_iter()).map(|(k, v, ptr)| {
                match v.t {
                    JsType::JsPtr(ref tag) => match ptr {
                        Some(ptr) => {
                            if tag.eq_ptr_type(&ptr) {
                                println!("HERE");
                                allocator.alloc(v.unique.clone(), ptr)
                                    .expect("Unable to allocate!"); // TODO better error handling
                            } else {
                                ptr_type_mismatch!(tag, ptr);
                            }
                        },
                        None => ptr_type_mismatch!(tag, None::<JsPtrEnum>),
                    },
                    _ => match ptr {
                        Some(ptr) => ptr_type_mismatch!(None::<JsPtrEnum>, ptr),
                        None => {},
                    },
                }
                (k, v)
            }).collect()
        }
    }

    pub fn add_key(&mut self, obj_binding: &UniqueBinding, k: JsKey, v: JsVar,
                   ptr: Option<JsPtrEnum>, allocator: &mut AllocBox) {
        if let Some(var) = self.dict.get(&k) {
            match var.t {
                JsType::JsPtr(_) => { allocator.condemn(var.unique.clone()).expect("Unable to whiten!") },
                _ => {}
            }
        }
        /*if let Some(ptr) = ptr {
            allocator.alloc(v.unique.clone(), ptr).expect("Unable to allocate!"); // TODO better error handling
        }*/

        match allocator.find_id(obj_binding) {
            Some(ref ptr) => match &mut *(ptr.borrow_mut()) {
                &mut JsPtrEnum::JsObj(ref mut obj) => obj.dict.insert(k.clone(), v.clone()),
                _ => panic!("Binding does not belong to an object!"),
            },
            None => panic!("No pointer with matching binding found!"),
        };

        self.dict.insert(k, v);
    }

    pub fn remove_key(&mut self, obj_binding: &UniqueBinding, k: &JsKey, allocator: &mut AllocBox)
        -> Option<(JsVar, Option<JsPtrEnum>)>{
        let var_opt = match &mut *(allocator.find_id(obj_binding).expect("No pointer with matching binding found!").borrow_mut()) {
            &mut JsPtrEnum::JsObj(ref mut obj) => obj.dict.remove(k),
            _ => panic!("Binding does not belong to an object!"),
        };

        if let Some(var) = var_opt {
            let ptr = allocator.find_id(&var.unique).map(|s| s.borrow().clone());

            if ptr.is_some() {
                allocator.condemn(var.unique.clone()).expect("Unable to whiten!");
            }

            let _ = self.dict.remove(k);
            Some((var, ptr))
        } else {
            None
        }
    }

    pub fn get_children(&self) -> HashSet<UniqueBinding> {
        let mut bindings = HashSet::new();
        for v in self.dict.values() {
            match v.t {
                JsType::JsPtr(_) => { bindings.insert(v.unique.clone()); },
                _ => (),
            }
        }
        bindings
    }
}

impl Display for JsObjStruct {
    fn fmt(&self, mut fmt: &mut Formatter) -> Result<(), Error> {
        try!(write!(fmt, "{{ "));

        for (i, (ref key, ref val)) in self.dict.iter().enumerate() {
            if i != 0 {
                try!(write!(fmt, ", "));
            }

            try!(write!(fmt, "{}: {}", key, val));
        }

        write!(fmt, " }}")
    }
}
<|endoftext|>


<commit_before>use snafu::Snafu;
use tracing::metadata::LevelFilter;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::EnvFilter;

#[derive(Debug, Snafu)]
pub enum Error {
    #[snafu(display("Could not init log file: {}", source))]
    InitLog { source: std::io::Error },
}

// FIXME Remove all expects
pub fn logger_init() -> Result<tracing_appender::non_blocking::WorkerGuard, Error> {
    let default_level = LevelFilter::INFO;
    let rust_log =
        std::env::var(EnvFilter::DEFAULT_ENV).unwrap_or_else(|_| default_level.to_string());

    let env_filter = EnvFilter::try_new(rust_log).unwrap_or_else(|err| {
        eprintln!(
            "invalid {}, falling back to level '{}' - {}",
            EnvFilter::DEFAULT_ENV,
            default_level,
            err,
        );
        EnvFilter::new(default_level.to_string())
    });

    let (non_blocking, guard) = tracing_appender::non_blocking(std::io::stdout());

    let subscriber = tracing_subscriber::registry()
        .with(tracing_subscriber::fmt::layer().with_writer(non_blocking))
        .with(env_filter);

    tracing::subscriber::set_global_default(subscriber)
        .expect("Failed to set global tracing subscriber.");

    Ok(guard)
}
<commit_msg>remove the name of file in logs<commit_after>use snafu::Snafu;
use tracing::metadata::LevelFilter;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::EnvFilter;

#[derive(Debug, Snafu)]
pub enum Error {
    #[snafu(display("Could not init log file: {}", source))]
    InitLog { source: std::io::Error },
}

// FIXME Remove all expects
pub fn logger_init() -> Result<tracing_appender::non_blocking::WorkerGuard, Error> {
    let default_level = LevelFilter::INFO;
    let rust_log =
        std::env::var(EnvFilter::DEFAULT_ENV).unwrap_or_else(|_| default_level.to_string());

    let env_filter = EnvFilter::try_new(rust_log).unwrap_or_else(|err| {
        eprintln!(
            "invalid {}, falling back to level '{}' - {}",
            EnvFilter::DEFAULT_ENV,
            default_level,
            err,
        );
        EnvFilter::new(default_level.to_string())
    });

    let (non_blocking, guard) = tracing_appender::non_blocking(std::io::stdout());

    let event_format = tracing_subscriber::fmt::format()
        .with_target(false)     // Don't include event targets.
        .compact();

    let subscriber = tracing_subscriber::registry()
        .with(
            tracing_subscriber::fmt::layer()
                .with_writer(non_blocking)
                .event_format(event_format)
        )
        .with(env_filter);

    tracing::subscriber::set_global_default(subscriber)
        .expect("Failed to set global tracing subscriber.");

    Ok(guard)
}
<|endoftext|>


<commit_before>use std::cmp::min;

use unicode_width::UnicodeWidthStr;

use buffer::Buffer;
use widgets::{Widget, Block};
use layout::Rect;
use style::Color;

pub struct List<'a> {
    block: Option<Block<'a>>,
    selected: usize,
    selection_symbol: Option<&'a str>,
    selection_color: Color,
    color: Color,
    background_color: Color,
    items: &'a [&'a str],
}

impl<'a> Default for List<'a> {
    fn default() -> List<'a> {
        List {
            block: None,
            selected: 0,
            selection_symbol: None,
            selection_color: Color::Reset,
            color: Color::Reset,
            background_color: Color::Reset,
            items: &[],
        }
    }
}

impl<'a> List<'a> {
    pub fn block(&'a mut self, block: Block<'a>) -> &mut List<'a> {
        self.block = Some(block);
        self
    }

    pub fn items(&'a mut self, items: &'a [&'a str]) -> &mut List<'a> {
        self.items = items;
        self
    }

    pub fn color(&'a mut self, color: Color) -> &mut List<'a> {
        self.color = color;
        self
    }

    pub fn background_color(&'a mut self, color: Color) -> &mut List<'a> {
        self.background_color = color;
        self
    }

    pub fn selection_symbol(&'a mut self, selection_symbol: &'a str) -> &mut List<'a> {
        self.selection_symbol = Some(selection_symbol);
        self
    }

    pub fn selection_color(&'a mut self, selection_color: Color) -> &mut List<'a> {
        self.selection_color = selection_color;
        self
    }

    pub fn select(&'a mut self, index: usize) -> &'a mut List<'a> {
        self.selected = index;
        self
    }
}

impl<'a> Widget for List<'a> {
    fn buffer(&self, area: &Rect, buf: &mut Buffer) {

        let list_area = match self.block {
            Some(ref b) => {
                b.buffer(area, buf);
                b.inner(area)
            }
            None => *area,
        };

        let list_length = self.items.len();
        let list_height = list_area.height as usize;
        let bound = min(list_height, list_length);
        let offset = if self.selected > list_height {
            min(self.selected - list_height, list_length - list_height)
        } else {
            0
        };
        let x = match self.selection_symbol {
            Some(s) => (s.width() + 1) as u16 + list_area.left(),
            None => list_area.left(),
        };
        for i in 0..bound {
            let index = i + offset;
            let item = self.items[index];
            let color = if index == self.selected {
                self.selection_color
            } else {
                self.color
            };
            buf.set_string(x,
                           list_area.top() + i as u16,
                           item,
                           color,
                           self.background_color);
        }
        if let Some(s) = self.selection_symbol {
            buf.set_string(list_area.left(),
                           list_area.top() + (self.selected - offset) as u16,
                           s,
                           self.selection_color,
                           self.background_color);
        }
    }
}
<commit_msg>Fix list selection<commit_after>use std::cmp::min;

use unicode_width::UnicodeWidthStr;

use buffer::Buffer;
use widgets::{Widget, Block};
use layout::Rect;
use style::Color;

pub struct List<'a> {
    block: Option<Block<'a>>,
    selected: usize,
    selection_symbol: Option<&'a str>,
    selection_color: Color,
    color: Color,
    background_color: Color,
    items: &'a [&'a str],
}

impl<'a> Default for List<'a> {
    fn default() -> List<'a> {
        List {
            block: None,
            selected: 0,
            selection_symbol: None,
            selection_color: Color::Reset,
            color: Color::Reset,
            background_color: Color::Reset,
            items: &[],
        }
    }
}

impl<'a> List<'a> {
    pub fn block(&'a mut self, block: Block<'a>) -> &mut List<'a> {
        self.block = Some(block);
        self
    }

    pub fn items(&'a mut self, items: &'a [&'a str]) -> &mut List<'a> {
        self.items = items;
        self
    }

    pub fn color(&'a mut self, color: Color) -> &mut List<'a> {
        self.color = color;
        self
    }

    pub fn background_color(&'a mut self, color: Color) -> &mut List<'a> {
        self.background_color = color;
        self
    }

    pub fn selection_symbol(&'a mut self, selection_symbol: &'a str) -> &mut List<'a> {
        self.selection_symbol = Some(selection_symbol);
        self
    }

    pub fn selection_color(&'a mut self, selection_color: Color) -> &mut List<'a> {
        self.selection_color = selection_color;
        self
    }

    pub fn select(&'a mut self, index: usize) -> &'a mut List<'a> {
        self.selected = index;
        self
    }
}

impl<'a> Widget for List<'a> {
    fn buffer(&self, area: &Rect, buf: &mut Buffer) {

        let list_area = match self.block {
            Some(ref b) => {
                b.buffer(area, buf);
                b.inner(area)
            }
            None => *area,
        };

        let list_length = self.items.len();
        let list_height = list_area.height as usize;
        let bound = min(list_height, list_length);
        let offset = if self.selected >= list_height {
            self.selected - list_height + 1
        } else {
            0
        };
        let x = match self.selection_symbol {
            Some(s) => (s.width() + 1) as u16 + list_area.left(),
            None => list_area.left(),
        };
        for i in 0..bound {
            let index = i + offset;
            let item = self.items[index];
            let color = if index == self.selected {
                self.selection_color
            } else {
                self.color
            };
            buf.set_string(x,
                           list_area.top() + i as u16,
                           item,
                           color,
                           self.background_color);
        }
        if let Some(s) = self.selection_symbol {
            buf.set_string(list_area.left(),
                           list_area.top() + (self.selected - offset) as u16,
                           s,
                           self.selection_color,
                           self.background_color);
        }
    }
}
<|endoftext|>