#![deny(missing_docs)]
use glob::glob;
use indexmap::IndexSet;
use log::info;
use snafu::{Backtrace, ResultExt, Snafu};
use std::fmt::Debug;
use std::fs::File;
use std::io::Read;
use std::path::Path;
type Result<T, E = Error> = std::result::Result<T, E>;
pub fn load_string<P: AsRef<Path>>(path: P) -> Result<String> {
let path = path.as_ref();
info!("Loading file {:?}", path);
let mut file = File::open(path).context(FileOpen {
path: path.to_path_buf(),
})?;
let mut content = String::new();
file.read_to_string(&mut content).context(FileRead {
path: path.to_path_buf(),
})?;
Ok(content)
}
pub fn load_json<T, P: AsRef<Path>>(path: P) -> Result<T>
where
T: serde::de::DeserializeOwned,
{
let path = path.as_ref();
info!("Loading file {:?}", path);
Ok(serde_json::from_reader(File::open(path).context(FileOpen {
path: path.to_path_buf(),
})?)
.context(JsonLoading {
path: path.to_path_buf(),
})?)
}
pub fn load_json_buffered<T, P: AsRef<Path>>(path: P) -> Result<T>
where
T: serde::de::DeserializeOwned,
{
let path = path.as_ref();
info!("Loading file {:?}", path);
let s = load_string(path)?;
Ok(serde_json::from_str(&s).context(JsonLoading {
path: path.to_path_buf(),
})?)
}
pub fn load_directory_listing<
T: std::str::FromStr + std::hash::Hash + std::cmp::Eq,
>(
pattern: &str,
) -> Result<IndexSet<T>> {
info!("Finding pattern: {}", pattern);
let listing = glob(pattern)
.context(GlobPattern {
pattern: pattern.to_string(),
})?
.filter_map(|r| r.ok())
.filter_map(|p| p.to_str().map(str::to_string))
.map(|p| diff_extract(pattern, &p))
.filter_map(|s| T::from_str(&s).ok())
.collect::<IndexSet<T>>();
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()
}
pub trait PathLoad: Sized {
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)
}
}
#[derive(Debug, Snafu)]
pub enum Error {
#[snafu(display("Error in glob pattern: {}: {}", pattern, source))]
GlobPattern {
pattern: String,
source: glob::PatternError,
backtrace: Backtrace,
},
#[snafu(display("Couldn't open file {}: {}", path.display(), source))]
FileOpen {
path: std::path::PathBuf,
source: std::io::Error,
backtrace: Backtrace,
},
#[snafu(display("Couldn't read from file {}: {}", path.display(), source))]
FileRead {
path: std::path::PathBuf,
source: std::io::Error,
backtrace: Backtrace,
},
#[snafu(display("Couldn't deserialize JSON from file {}: {}", path.display(), source))]
JsonLoading {
path: std::path::PathBuf,
source: serde_json::Error,
backtrace: Backtrace,
},
}
#[cfg(test)]
mod tests {
use indexmap::IndexSet;
use log::info;
use std::fs::{create_dir, File};
use std::io::Write;
use tempfile::tempdir;
#[test]
fn load_string_from_path() {
use crate::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 crate::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 crate::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::<IndexSet<String>>();
info!("Listing: {:#?}", listing);
assert_eq!(listing, expected);
}
}