summaryrefslogtreecommitdiffstats
path: root/src/tools/suggest-tests/src/lib.rs
blob: 1c1d9d0333ddb51c3e33b2b5bea95dd525447502 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
use std::{
    fmt::{self, Display},
    path::Path,
};

use dynamic_suggestions::DYNAMIC_SUGGESTIONS;
use glob::Pattern;
use static_suggestions::STATIC_SUGGESTIONS;

mod dynamic_suggestions;
mod static_suggestions;

#[cfg(test)]
mod tests;

macro_rules! sug {
    ($cmd:expr) => {
        Suggestion::new($cmd, None, &[])
    };

    ($cmd:expr, $paths:expr) => {
        Suggestion::new($cmd, None, $paths.as_slice())
    };

    ($cmd:expr, $stage:expr, $paths:expr) => {
        Suggestion::new($cmd, Some($stage), $paths.as_slice())
    };
}

pub(crate) use sug;

pub fn get_suggestions<T: AsRef<str>>(modified_files: &[T]) -> Vec<Suggestion> {
    let mut suggestions = Vec::new();

    // static suggestions
    for (globs, sugs) in STATIC_SUGGESTIONS.iter() {
        let globs = globs
            .iter()
            .map(|glob| Pattern::new(glob).expect("Found invalid glob pattern!"))
            .collect::<Vec<_>>();
        let matches_some_glob = |file: &str| globs.iter().any(|glob| glob.matches(file));

        if modified_files.iter().map(AsRef::as_ref).any(matches_some_glob) {
            suggestions.extend_from_slice(sugs);
        }
    }

    // dynamic suggestions
    for sug in DYNAMIC_SUGGESTIONS {
        for file in modified_files {
            let sugs = sug(Path::new(file.as_ref()));

            suggestions.extend_from_slice(&sugs);
        }
    }

    suggestions.sort();
    suggestions.dedup();

    suggestions
}

#[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Debug)]
pub struct Suggestion {
    pub cmd: String,
    pub stage: Option<u32>,
    pub paths: Vec<String>,
}

impl Suggestion {
    pub fn new(cmd: &str, stage: Option<u32>, paths: &[&str]) -> Self {
        Self { cmd: cmd.to_owned(), stage, paths: paths.iter().map(|p| p.to_string()).collect() }
    }

    pub fn with_single_path(cmd: &str, stage: Option<u32>, path: &str) -> Self {
        Self::new(cmd, stage, &[path])
    }
}

impl Display for Suggestion {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        write!(f, "{} ", self.cmd)?;

        for path in &self.paths {
            write!(f, "{} ", path)?;
        }

        if let Some(stage) = self.stage {
            write!(f, "{}", stage)?;
        } else {
            // write a sentinel value here (in place of a stage) to be consumed
            // by the shim in bootstrap, it will be read and ignored.
            write!(f, "N/A")?;
        }

        Ok(())
    }
}