Refactor task database
This commit is contained in:
parent
9ae8eaf3c2
commit
b28c97fdb1
113
src/db.rs
113
src/db.rs
|
@ -1,83 +1,6 @@
|
|||
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);
|
||||
}
|
||||
use std::path::Path;
|
||||
|
||||
/// Reads the file and strips whitespace (including newlines)
|
||||
/// Useful for file-based key-value store
|
||||
|
@ -97,6 +20,12 @@ pub fn read_or_none(path: &Path) -> Option<String> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn read_extension(path: &Path, setting: &str) -> Option<String> {
|
||||
let mut path = path.to_path_buf();
|
||||
path.set_extension(setting);
|
||||
read_or_none(&path)
|
||||
}
|
||||
|
||||
/// Returns true when the file exists and has user exec
|
||||
/// permission. Returns false otherwise.
|
||||
pub fn is_executable(path: &Path) -> bool {
|
||||
|
@ -120,31 +49,3 @@ pub fn is_executable(path: &Path) -> bool {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
30
src/main.rs
30
src/main.rs
|
@ -14,25 +14,35 @@ fn main() -> Result<(), std::io::Error> {
|
|||
|
||||
let mut context = HashMap::new();
|
||||
|
||||
let base_dir = cmd.basedir();
|
||||
let base_dir = match base_dir.canonicalize() {
|
||||
Ok(p) => p.to_str().unwrap().to_string(),
|
||||
let basedir = cmd.basedir();
|
||||
let basedir = match basedir.canonicalize() {
|
||||
Ok(p) => p,
|
||||
Err(_) => {
|
||||
context.insert("i18n_basedir", base_dir.to_str().unwrap());
|
||||
context.insert("i18n_basedir", basedir.to_str().unwrap());
|
||||
log::error("missing_basedir", Some(&context));
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
set_var("GITBUILDDIR", &base_dir);
|
||||
let basedir_str = basedir.to_str().unwrap().to_string();
|
||||
|
||||
set_var("GITBUILDDIR", &basedir);
|
||||
|
||||
let mut tasks = if cmd.tasks.is_empty() {
|
||||
task::from_dir(&base_dir).expect("Could not load DB")
|
||||
task::from_dir(&basedir).expect("Could not load DB")
|
||||
} else {
|
||||
task::from_dir_and_list(&base_dir, cmd.tasks).expect("Could not load given tasks")
|
||||
match task::from_dir_and_list(&basedir, cmd.tasks) {
|
||||
Ok(t) => t,
|
||||
Err(task::MissingTask(t)) => {
|
||||
context.insert("i18n_task", &t);
|
||||
log::error("unknown_arg", Some(&context));
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
let (config_folder, ignored_tasks) = task::config(&std::path::Path::new(&base_dir));
|
||||
let (config_folder, ignored_tasks) = task::config(&basedir);
|
||||
|
||||
set_var("GITBUILDCONF", &config_folder);
|
||||
|
||||
|
@ -53,7 +63,7 @@ fn main() -> Result<(), std::io::Error> {
|
|||
// Maybe the task has a source we should clone?
|
||||
if let Some(repo) = &task.repo {
|
||||
context.insert("$source", &repo.source);
|
||||
let source_dir = format!("{}/.{}", base_dir, &task.name);
|
||||
let source_dir = format!("{}/.{}", basedir_str, &task.name);
|
||||
if task.cloned == false {
|
||||
log::info("clone", Some(&context));
|
||||
if !repo.clone() {
|
||||
|
@ -81,7 +91,7 @@ fn main() -> Result<(), std::io::Error> {
|
|||
}
|
||||
} else {
|
||||
// No source, chaneg working dir to basedir
|
||||
cd(&base_dir).expect("Failed to change working dir");
|
||||
cd(&basedir).expect("Failed to change working dir");
|
||||
println!("Taks {} doesn't have a source, run it", task.name);
|
||||
task.run_once();
|
||||
}
|
||||
|
|
88
src/task.rs
88
src/task.rs
|
@ -2,8 +2,7 @@ use std::collections::HashMap;
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
use crate::db;
|
||||
use crate::db::{is_executable, Entry};
|
||||
use crate::db::{is_executable, read_extension};
|
||||
use crate::dvcs::{Backend, Repo};
|
||||
use crate::log;
|
||||
|
||||
|
@ -47,23 +46,31 @@ pub fn config(basedir: &Path) -> (PathBuf, Vec<String>) {
|
|||
(basedir.join("config"), Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl Task {
|
||||
pub fn from_entry(entry: &Entry) -> Task {
|
||||
let source = entry.read_setting("source");
|
||||
let dest = entry
|
||||
.base_dir
|
||||
.join(&format!(".{}", entry.name.to_str().expect("WTF")));
|
||||
let cloned = source.clone().map_or(false, |_| dest.is_dir());
|
||||
let subupdates = entry.read_setting("subupdates").is_some();
|
||||
pub fn from_path(path: &Path) -> Option<Task> {
|
||||
let name = path.file_name().unwrap().to_str().unwrap().to_string();
|
||||
|
||||
Task {
|
||||
name: entry.name.to_str().unwrap().to_string(),
|
||||
bin: entry.path.clone(),
|
||||
// We don't return a task if:
|
||||
// - the path is not a file
|
||||
// - the file is not executable (can't be run)
|
||||
// - the file starts with . (hidden file)
|
||||
if !path.is_file() || !is_executable(&path) || name.starts_with('.') {
|
||||
return None;
|
||||
}
|
||||
|
||||
let basedir = path.parent().unwrap(); // Calling a task in / (FS root) will panic
|
||||
let source = read_extension(path, "source");
|
||||
let dest = source_dir_from_basedir(&basedir, &name);
|
||||
let cloned = source.clone().map_or(false, |_| dest.is_dir());
|
||||
let subupdates = read_extension(path, "subupdates").is_some();
|
||||
|
||||
Some(Task {
|
||||
name,
|
||||
bin: path.to_path_buf(),
|
||||
// None source = None repo
|
||||
repo: source.as_ref().map(|s| {
|
||||
Repo::new(
|
||||
Backend::from_setting(entry.read_setting("dvcs")),
|
||||
Backend::from_setting(read_extension(path, "dvcs")),
|
||||
s,
|
||||
&dest,
|
||||
subupdates,
|
||||
|
@ -71,13 +78,13 @@ impl Task {
|
|||
}),
|
||||
source,
|
||||
config: HashMap::new(),
|
||||
branch: entry.read_setting("checkout"),
|
||||
hosts: entry.read_setting("hosts").map_or(Vec::new(), |c| {
|
||||
branch: read_extension(path, "checkout"),
|
||||
hosts: read_extension(path, "hosts").map_or(Vec::new(), |c| {
|
||||
c.split("\n").map(|line| line.to_string()).collect()
|
||||
}),
|
||||
cloned,
|
||||
subupdates: entry.read_setting("subupdates").is_some(),
|
||||
}
|
||||
subupdates: read_extension(path, "subupdates").is_some(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn checkout(&self) {
|
||||
|
@ -151,29 +158,40 @@ impl Task {
|
|||
}
|
||||
}
|
||||
|
||||
// Takes an already instanced database (ie Vec<Entry>)
|
||||
// to turn into a list of Tasks
|
||||
pub fn from_entries(db: Vec<Entry>) -> Vec<Task> {
|
||||
db.iter().map(|x| Task::from_entry(&x)).collect()
|
||||
/// Loads a task list from a given base directory. Fails if the directory
|
||||
/// is not readable with std::io::Error.
|
||||
pub fn from_dir(basedir: &Path) -> Result<Vec<Task>, std::io::Error> {
|
||||
Ok(
|
||||
basedir.read_dir()?.filter_map(|f| {
|
||||
if f.is_err() {
|
||||
// Dismiss individual file errors
|
||||
return None;
|
||||
}
|
||||
return Task::from_path(&f.unwrap().path());
|
||||
}).collect()
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a list of tasks, or std::io::Error
|
||||
/// Reads all entries in a directory
|
||||
pub fn from_dir(base_dir: &str) -> Result<Vec<Task>, std::io::Error> {
|
||||
Ok(from_entries(db::from(base_dir, is_executable)?))
|
||||
}
|
||||
pub struct MissingTask(pub String);
|
||||
|
||||
/// Returns a list of tasks, or std::io::Error
|
||||
/// Reads entries in a given list from a directory, fails if a requested entry doesn't exist
|
||||
/// (does not load the whole folder)
|
||||
pub fn from_dir_and_list(basedir: &str, list: Vec<String>) -> Result<Vec<Task>, db::Error> {
|
||||
/// Loads a task list from a given base directory, taking only tasks that are in requested list.
|
||||
/// Fails if the directory is not readable with std::io::Error, or if a requested task does
|
||||
/// not exist.
|
||||
pub fn from_dir_and_list(basedir: &Path, list: Vec<String>) -> Result<Vec<Task>, MissingTask> {
|
||||
let mut tasks = Vec::new();
|
||||
for task in list {
|
||||
if let Some(entry) = db::entry(&basedir, is_executable, &task) {
|
||||
tasks.push(Task::from_entry(&entry));
|
||||
for t in list {
|
||||
if let Some(task) = Task::from_path(&basedir.join(&t)) {
|
||||
tasks.push(task);
|
||||
} else {
|
||||
return Err(db::Error::EntryNotFound(task.clone()));
|
||||
return Err(MissingTask(t));
|
||||
}
|
||||
}
|
||||
Ok(tasks)
|
||||
}
|
||||
|
||||
/// Takes a &Path to a basedir, and a &str task_name, and return
|
||||
/// the corresponding source directory as a PathBuf. Does not check
|
||||
/// if the target exists.
|
||||
pub fn source_dir_from_basedir(basedir: &Path, task_name: &str) -> PathBuf {
|
||||
basedir.join(&format!(".{}", task_name))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue