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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#![deny(missing_docs)]

//! File system helpers used in OMMUI.

#[cfg(test)]
extern crate tempfile;

#[macro_use]
extern crate failure;
#[macro_use]
extern crate log;

extern crate diff;
extern crate glob;
extern crate serde;
extern crate serde_json;

mod error;

use glob::glob;
use std::collections::BTreeSet;
use std::fmt::Debug;
use std::fs::File;
use std::io::Read;
use std::path::Path;

pub use error::{Error,ErrorKind};

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

/// Load a string from a file.
pub fn load_string<P: AsRef<Path> + Debug>(path: P) -> Result<String> {
    info!("Loading file {:?}", path);

    let mut file = File::open(path)?;
    let mut content = String::new();
    file.read_to_string(&mut content)?;
    Ok(content)
}

/// Load a json data structure from a file.
pub fn load_json<T, P: AsRef<Path> + Debug>(path: P) -> Result<T>
where
    T: serde::de::DeserializeOwned,
{
    info!("Loading file {:?}", path);
    Ok(serde_json::from_reader(File::open(path)?)?)
}

/// Load a `BTreeSet` from a directory, one entry for each glob match.
///
/// This function takes a glob pattern that determines which entries
/// get put into the set. The entries in the set contain the parts
/// that differ from the pattern.
///
/// For example if the directory `/srv/data` contains the files
/// `hello.json`, `world.json` and `readme.txt`, and the glob pattern
/// is `/srv/data/*.json`, then the resulting set will contain
/// `hello` and `world`.
pub fn load_directory_listing(pattern: &str) -> Result<BTreeSet<String>> {
    info!("Finding pattern: {}", pattern);

    let listing = glob(pattern)?
        .filter_map(|r| r.ok())
        .filter_map(|p| p.to_str().map(str::to_string))
        .map(|p| diff_extract(pattern, &p))
        .collect::<BTreeSet<String>>();

    Ok(listing)
}

fn diff_is_right<T>(r: &diff::Result<T>) -> bool {
    match *r {
        diff::Result::Right(_) => true,
        diff::Result::Left(_) | diff::Result::Both(_, _) => false,
    }
}

fn diff_extract_item<T>(r: &diff::Result<T>) -> &T {
    match *r {
        diff::Result::Right(ref t)
        | diff::Result::Left(ref t)
        | diff::Result::Both(ref t, _) => t,
    }
}

fn diff_extract(a: &str, b: &str) -> String {
    let d = diff::chars(a, b);
    d.iter()
        .filter(|r| diff_is_right(*r))
        .map(|r| diff_extract_item(r))
        .collect()
}

/// A trait defining a method for loading data structure from a path.
pub trait PathLoad: Sized {
    /// Load the data structure from a path.
    fn load_from_path<P: AsRef<Path> + Debug>(path: P) -> Result<Self>;
}

impl<T> PathLoad for T
where
    T: serde::de::DeserializeOwned,
{
    fn load_from_path<P: AsRef<Path> + Debug>(path: P) -> Result<T> {
        load_json::<T, P>(path)
    }
}

#[cfg(test)]
mod tests {
    use std::collections::BTreeSet;
    use std::fs::{create_dir, File};
    use std::io::Write;
    use tempfile::tempdir;

    #[test]
    fn load_string_from_path() {
        use load_string;

        let dir = tempdir().unwrap();

        let path = dir.path().join("example.txt");

        {
            let mut file = File::create(&path).unwrap();
            file.write(b"hello world").unwrap();
        }

        {
            let s = load_string(&path).unwrap();
            assert_eq!(s, "hello world".to_string());
        }
    }

    #[test]
    fn load_string_from_path_string() {
        use load_string;

        let dir = tempdir().unwrap();

        let path =
            dir.path().join("example.txt").to_string_lossy().to_string();

        {
            let mut file = File::create(&path).unwrap();
            file.write(b"hello world").unwrap();
        }

        {
            let s = load_string(&path).unwrap();
            assert_eq!(s, "hello world".to_string());
        }
    }

    #[test]
    fn load_directory_listing() {
        use load_directory_listing;
        let dir = tempdir().unwrap();

        create_dir(dir.path().join("hello_world.new")).unwrap();
        create_dir(dir.path().join("hello_neighbour.new")).unwrap();
        create_dir(dir.path().join("hello_neighbour")).unwrap();
        create_dir(dir.path().join("xhello_neighbour.new")).unwrap();

        let dir = dir.path().to_str().unwrap();
        let pattern = format!("{}/hello_*.new", dir);
        let listing = load_directory_listing(&pattern).unwrap();

        let expected = ["world", "neighbour"]
            .into_iter()
            .map(|s| String::from(*s))
            .collect::<BTreeSet<String>>();
        info!("Listing: {:#?}", listing);
        assert_eq!(listing, expected);
    }
}