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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
//! Your favorite all-in-one formatter tool!

#![deny(missing_docs)]
pub mod command;
pub mod config;
pub mod engine;
pub mod eval_cache;
pub mod formatter;

use anyhow::Result;
use filetime::FileTime;
use path_clean::PathClean;
use serde::{Deserialize, Serialize};
use std::env;
use std::fs::Metadata;
use std::path::{Path, PathBuf};
use which::which_in;

/// FileMeta represents file meta that change on file modification
/// It currently stores a file mtime and size tuple.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Copy, Clone)]
pub struct FileMeta {
    /// File mtime
    pub mtime: i64,
    /// File size
    pub size: u64,
}

/// Small utility that stat() and retrieve the mtime of a file path
pub fn get_path_meta(path: &Path) -> Result<FileMeta> {
    let metadata = std::fs::metadata(path)?;
    Ok(get_meta(&metadata))
}

/// Small utility that stat() and retrieve the mtime of a file metadata
#[must_use]
pub fn get_meta(metadata: &Metadata) -> FileMeta {
    FileMeta {
        mtime: FileTime::from_last_modification_time(metadata).unix_seconds(),
        size: metadata.len(),
    }
}

/// Resolve the command into an absolute path.
fn expand_exe(command: &str, reference: &Path) -> Result<PathBuf> {
    Ok(which_in(command, env::var_os("PATH"), reference)?.clean())
}

/// Returns an absolute path. If the path is absolute already, leave it alone. Otherwise join it to the reference path.
/// Then clean all superfluous ../
#[must_use]
pub fn expand_path(path: &Path, reference: &Path) -> PathBuf {
    let new_path = if path.is_absolute() {
        path.to_path_buf()
    } else {
        reference.join(path)
    };
    new_path.clean()
}

/// Only expands the path if the string contains a slash (/) in it. Otherwise consider it as a string.
pub fn expand_if_path(str: String, reference: &Path) -> String {
    if str.contains('/') {
        expand_path(Path::new(&str), reference)
            .to_string_lossy()
            .to_string()
    } else {
        format!("*/{}", str)
    }
}

#[cfg(test)]
mod tests {
    use super::expand_if_path;
    use globset::{GlobBuilder, GlobSetBuilder};
    use std::path::Path;

    #[test]
    fn test_expand_if_path_single_pattern() {
        let path = vec![
            "/Foo.hs",
            "/nested/Foo.hs",
            "/nested/nested_again/Foo.hs",
            "/different_folder/Foo.hs",
            "/nested/different_folder/Foo.hs",
        ];
        let pattern = "Foo.hs";
        let mut sum = GlobSetBuilder::new();
        let tree_root = Path::new("/");
        let pat = expand_if_path(pattern.to_string(), &tree_root);
        let glob = GlobBuilder::new(&pat).build().unwrap();
        sum.add(glob);
        let result = sum.build().unwrap();
        let test = path
            .clone()
            .into_iter()
            .filter(|p| result.is_match(p))
            .collect::<Vec<&str>>();

        assert_eq!(path, test);
    }

    #[test]
    fn test_expand_if_path_wildcard() {
        let path = vec![
            "/Foo.hs",
            "/nested/Foo.hs",
            "/nested/nested_again/Foo.hs",
            "/different_folder/Foo.hs",
            "/nested/different_folder/Foo.hs",
            "/nested/Bar.hs",
            "/nested/different_folder/Bar.hs",
        ];

        let pattern = "*.hs";
        let mut sum = GlobSetBuilder::new();
        let tree_root = Path::new("/");
        let pat = expand_if_path(pattern.to_string(), &tree_root);
        let glob = GlobBuilder::new(&pat).build().unwrap();
        sum.add(glob);
        let result = sum.build().unwrap();
        let test = path
            .clone()
            .into_iter()
            .filter(|p| result.is_match(p))
            .collect::<Vec<&str>>();

        assert_eq!(path, test);
    }

    #[test]
    fn test_expand_if_path_single_pattern_with_slash() {
        let path = vec![
            "/Foo.hs",
            "/nested/Foo.hs",
            "/nested/nested/Foo.hs",
            "/different_folder/Foo.hs",
            "/nested/different_folder/Foo.hs",
            "/nested/Bar.hs",
            "/nested/different_folder/Bar.hs",
        ];
        let pattern = "nested/Foo.hs";
        let mut sum = GlobSetBuilder::new();
        let tree_root = Path::new("/");
        let pat = expand_if_path(pattern.to_string(), &tree_root);
        let glob = GlobBuilder::new(&pat).build().unwrap();
        sum.add(glob);
        let result = sum.build().unwrap();
        let test = path
            .into_iter()
            .filter(|p| result.is_match(p))
            .collect::<Vec<&str>>();

        assert_eq!(vec!["/nested/Foo.hs"], test);
    }
}