build.rs/src/db.rs

151 lines
3.8 KiB
Rust

use std::ffi::OsString;
use std::fs;
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub enum Error {
EntryNotFound(String),
}
#[derive(Debug, Clone)]
pub struct Entry {
pub name: OsString,
pub path: PathBuf,
pub base_dir: PathBuf,
}
impl Entry {
pub fn new(path: PathBuf, name: OsString, base_dir: PathBuf) -> Entry {
Entry {
path,
name,
base_dir,
}
}
pub fn read_setting(&self, setting: &str) -> Option<String> {
let mut path = self.path.clone();
path.set_extension(setting);
read_or_none(&path)
}
}
/// Load single entry from folder
pub fn entry(basedir: &str, filter: impl Fn(&Path) -> bool, name: &str) -> Option<Entry> {
let basepath = PathBuf::from(&basedir);
let path = basepath.clone().join(name);
if !path.exists() || !filter(&path) {
return None;
}
Some(Entry::new(
path.clone(),
path.file_name()
.expect("Failed to read file name")
.to_os_string(),
basepath,
))
}
/// Loads entire database from folder
pub fn from(path_name: &str, filter: impl Fn(&Path) -> bool) -> Result<Vec<Entry>, std::io::Error> {
let path = PathBuf::from(&path_name);
let mut entries = Vec::new();
for file in path.read_dir()? {
if file.is_err() {
// Dismiss individual errors (in case there's a permission problem)
// TODO: maybe print a warning? Actually a configurable error level
// (using enum variants) should be passed to the function to configure
// whether to continue silently or error out for individual files
continue;
}
let entry = file?.path();
if filter(&entry) {
entries.push(Entry::new(
entry.clone(),
entry
.file_name()
.expect("Failed to read file name")
.to_os_string(),
path.clone(),
));
}
}
return Ok(entries);
}
/// Reads the file and strips whitespace (including newlines)
/// Useful for file-based key-value store
pub fn read_or_none(path: &Path) -> Option<String> {
if !path.is_file() {
return None;
}
match fs::read_to_string(path) {
Ok(content) => {
// Remove trailing space/newlines
Some(content.trim().to_string())
}
Err(e) => {
eprintln!("IO ERROR: {}", e);
None
}
}
}
/// Returns true when the file exists and has user exec
/// permission. Returns false otherwise.
pub fn is_executable(path: &Path) -> bool {
// Do not match directories
if !path.is_file() {
return false;
}
match fs::metadata(path) {
Ok(stat) => {
let mode = stat.mode();
// Check user exec permission)
if (mode & 0o100) == 0o100 {
true
} else {
false
}
}
Err(e) => {
eprintln!("IO Error: {}", e);
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_load_db() {
let base_dir = "tests/success";
let entries = from(base_dir, is_executable).expect("Could not load db");
let entries_names: Vec<String> = entries
.iter()
.map(|x| x.name.clone().into_string().unwrap())
.collect();
let expected: Vec<String> = vec!["task", "symlink", "no_source"]
.iter()
.map(|x| x.to_string())
.collect();
assert_eq!(expected.len(), entries_names.len());
for entry in expected {
if !entries_names.contains(&entry.to_string()) {
panic!("Could not find {}", &entry);
}
}
}
}