Add support for --inbox/--inbox-folder
This commit is contained in:
parent
697173bb81
commit
34a5840aa0
2
spec
2
spec
|
@ -1 +1 @@
|
|||
Subproject commit c0e3bc649743b53a19c549bb0bfd66e2b62f2866
|
||||
Subproject commit 891b8aabbf4da2e25fc66df503b85266485b8020
|
|
@ -22,6 +22,12 @@ pub struct Cli {
|
|||
#[structopt(short = "b", long = "basedir")]
|
||||
pub basedir: Option<String>,
|
||||
|
||||
#[structopt(long = "inbox")]
|
||||
pub inbox: bool,
|
||||
|
||||
#[structopt(long = "inbox-folder")]
|
||||
pub inboxdir: Option<String>,
|
||||
|
||||
//#[structopt(def)]
|
||||
pub tasks: Vec<String>,
|
||||
}
|
||||
|
|
38
src/main.rs
38
src/main.rs
|
@ -1,4 +1,5 @@
|
|||
use std::env::{set_current_dir as cd, set_var};
|
||||
use std::process::exit;
|
||||
|
||||
mod cli;
|
||||
mod db;
|
||||
|
@ -7,6 +8,7 @@ mod log;
|
|||
mod task;
|
||||
|
||||
use log::Context;
|
||||
use task::Select;
|
||||
|
||||
fn main() -> Result<(), std::io::Error> {
|
||||
let mut context = Context::new();
|
||||
|
@ -16,20 +18,30 @@ fn main() -> Result<(), std::io::Error> {
|
|||
|
||||
set_var("GITBUILDDIR", &basedir);
|
||||
|
||||
let mut tasks = if cmd.tasks.is_empty() {
|
||||
log::info("no_task", &context);
|
||||
task::from_dir(&basedir, &context).expect("Could not load DB")
|
||||
} else {
|
||||
match task::from_dir_and_list(&basedir, cmd.tasks, &context) {
|
||||
Ok(t) => t,
|
||||
Err(task::MissingTask(t)) => {
|
||||
// Temporarily override the global context
|
||||
let mut context = context.clone();
|
||||
context.insert("$i18n_arg".to_string(), t);
|
||||
log::error("unknown_arg", &context);
|
||||
std::process::exit(1);
|
||||
}
|
||||
if (cmd.force && cmd.inbox) || (cmd.inbox && cmd.inboxdir.is_some()) || (cmd.force && cmd.inboxdir.is_some()) {
|
||||
println!("CONFLICTING COMMANDS: You can only run --inbox, --inbox-dir or --forge. These options cannot be combined");
|
||||
exit(2);
|
||||
}
|
||||
|
||||
// Setup a filter for the tasks to load/run
|
||||
let select = if cmd.inbox {
|
||||
Select::Inbox(basedir.clone())
|
||||
} else if let Some(inboxdir) = cmd.inboxdir {
|
||||
Select::InboxDir(basedir.clone(), inboxdir)
|
||||
} else if cmd.tasks.is_empty() {
|
||||
log::info("no_task", &context);
|
||||
Select::All(basedir.clone())
|
||||
} else {
|
||||
Select::List(basedir.clone(), cmd.tasks.clone())
|
||||
};
|
||||
|
||||
// Load requested tasks
|
||||
let mut tasks = match select.apply(&context) {
|
||||
Ok(t) => t,
|
||||
Err(task::MissingTask(t)) => {
|
||||
context.insert("$i18n_arg".to_string(), t);
|
||||
log::error("unknown_arg", &context);
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
141
src/task.rs
141
src/task.rs
|
@ -6,18 +6,115 @@ use crate::db::{is_executable, read_extension, read_or_none};
|
|||
use crate::backend::{Repo, backend};
|
||||
use crate::log;
|
||||
|
||||
/// Defines how tasks are selected to be run
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Select {
|
||||
/// Select all tasks (executable files) from a given basedir
|
||||
All(PathBuf),
|
||||
/// Select all tasks with a matching TASK.inbox from a basedir
|
||||
Inbox(PathBuf),
|
||||
/// Select all tasks from a basedir with a matching TASK.inbox
|
||||
/// in a separate inbox directory.
|
||||
InboxDir(PathBuf, String),
|
||||
/// Select all tasks from a basedir that are also in a list
|
||||
List(PathBuf, Vec<String>),
|
||||
}
|
||||
|
||||
impl Select {
|
||||
/// Removes the inbox file for the given task
|
||||
pub fn clean(&self, task: &str) {
|
||||
match self {
|
||||
Select::Inbox(basedir) => {
|
||||
std::fs::remove_file(&basedir.join(&format!("{}.inbox", task))).unwrap();
|
||||
},
|
||||
Select::InboxDir(_basedir, inboxdir) => {
|
||||
std::fs::remove_file(&PathBuf::from(inboxdir).join(&format!("{}.inbox", task))).unwrap()
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Find tasks matched by a selection
|
||||
pub fn apply(&self, context: &log::Context) -> Result<Vec<Task>, MissingTask> {
|
||||
// TODO: Dedicated error type for specific IO errors:
|
||||
// - missing basedir/select
|
||||
// - permissions problem for basedir/select
|
||||
match self {
|
||||
// We load all executable entries from basedir
|
||||
Select::All(basedir) => {
|
||||
Ok(from_dir(basedir, self.clone(), context))
|
||||
},
|
||||
|
||||
// We load all entries ending with .inbox from basedir
|
||||
Select::Inbox(basedir) => {
|
||||
let inbox_entries = basedir.read_dir().unwrap().filter_map(|f| {
|
||||
if f.is_err() {
|
||||
// Dismiss individual file errors
|
||||
return None;
|
||||
}
|
||||
let f_string = f.unwrap().file_name().to_str().unwrap().to_string();
|
||||
if ! f_string.ends_with(".inbox") || f_string.starts_with(".") {
|
||||
// We're only looking for non-hidden *.inbox files
|
||||
return None;
|
||||
}
|
||||
return Some(f_string.trim_end_matches(".inbox").to_string());
|
||||
}).collect();
|
||||
from_dir_and_list(basedir, inbox_entries, self.clone(), context)
|
||||
},
|
||||
|
||||
// We load all entries ending with .inbox from inboxdir
|
||||
Select::InboxDir(basedir, inboxdir) => {
|
||||
let inbox_entries = PathBuf::from(inboxdir).read_dir().unwrap().filter_map(|f| {
|
||||
if f.is_err() {
|
||||
// Dismiss individual file errors
|
||||
return None;
|
||||
}
|
||||
let f_string = f.unwrap().file_name().to_str().unwrap().to_string();
|
||||
if ! f_string.ends_with(".inbox") || f_string.starts_with(".") {
|
||||
// We're only looking for non-hidden *.inbox files
|
||||
return None;
|
||||
}
|
||||
return Some(f_string.trim_end_matches(".inbox").to_string());
|
||||
}).collect();
|
||||
from_dir_and_list(basedir, inbox_entries, self.clone(), context)
|
||||
},
|
||||
|
||||
// Load all entries from list
|
||||
Select::List(basedir, list) => {
|
||||
from_dir_and_list(basedir, list.clone(), self.clone(), context)
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Maybe all source/DVCS information should be moved to Repo
|
||||
// so that task structure is simpler.
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Task {
|
||||
/// The filename for the task
|
||||
pub name: String,
|
||||
/// Full path to the task
|
||||
pub bin: PathBuf,
|
||||
/// Potentially, a source repository to track for updates
|
||||
pub source: Option<String>,
|
||||
/// The full Repo information
|
||||
pub repo: Option<Repo>,
|
||||
/// Variables for task config
|
||||
pub config: HashMap<String, String>,
|
||||
/// Potentially, a branch/commit to track
|
||||
pub branch: Option<String>,
|
||||
/// List of hosts on which this task should run
|
||||
pub hosts: Vec<String>,
|
||||
/// Whether the source, if any, has been cloned already
|
||||
pub cloned: bool,
|
||||
/// Whether to track subrepository/submodule updates
|
||||
pub subupdates: bool,
|
||||
/// The context in which to store variables for translations
|
||||
pub context: log::Context,
|
||||
/// The selection context in which a task was created, so that running it can remove it from inbox
|
||||
pub select: Select,
|
||||
}
|
||||
|
||||
/// config returns an option of (settings directory, ignored tasks) as
|
||||
|
@ -48,7 +145,7 @@ pub fn config(basedir: &Path) -> (PathBuf, Vec<String>) {
|
|||
}
|
||||
}
|
||||
impl Task {
|
||||
pub fn from_path(path: &Path, context: &log::Context) -> Option<Task> {
|
||||
pub fn from_path(path: &Path, select: Select, context: &log::Context) -> Option<Task> {
|
||||
let name = path.file_name().unwrap().to_str().unwrap().to_string();
|
||||
|
||||
// We don't return a task if:
|
||||
|
@ -103,6 +200,7 @@ impl Task {
|
|||
cloned,
|
||||
subupdates: read_extension(path, "subupdates").is_some(),
|
||||
context,
|
||||
select,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -153,6 +251,9 @@ impl Task {
|
|||
|
||||
self.info("run");
|
||||
|
||||
// TODO: debug message for removing inbox
|
||||
self.select.clean(&self.name);
|
||||
|
||||
let cmd_out = Command::new("bash") // TODO: no need to call bash?
|
||||
.arg(&self.bin)
|
||||
.arg(&self.name)
|
||||
|
@ -197,19 +298,6 @@ impl Task {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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, context: &log::Context) -> 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(), context);
|
||||
}).collect()
|
||||
)
|
||||
}
|
||||
|
||||
pub struct MissingTask(pub String);
|
||||
|
||||
|
@ -259,19 +347,21 @@ impl SourceSet {
|
|||
/// Loads a task list from a given base directory, taking only tasks that are in requested list.
|
||||
/// Given tasks can be either a task name or a task URL. This function will panic if the basedir
|
||||
/// does not exist, or error if a requested task/source does not exist.
|
||||
pub fn from_dir_and_list(basedir: &Path, list: Vec<String>, context: &log::Context) -> Result<Vec<Task>, MissingTask> {
|
||||
// Safe unwrap because we checked that basedir existed before
|
||||
// or maybe can crash for permission problem? TODO: write tests
|
||||
pub fn from_dir_and_list(basedir: &Path, list: Vec<String>, select: Select, context: &log::Context) -> Result<Vec<Task>, MissingTask> {
|
||||
// TODO: Write tests for permissions problems
|
||||
|
||||
// If we're looking up specific tasks, maybe they're referenced by source
|
||||
// and not by name. SourceSet allows for a source->name mapping.
|
||||
let sourceset = SourceSet::from(basedir).unwrap();
|
||||
let mut tasks = Vec::new();
|
||||
for t in list {
|
||||
if let Some(task) = Task::from_path(&basedir.join(&t), context) {
|
||||
if let Some(task) = Task::from_path(&basedir.join(&t), select.clone(), context) {
|
||||
tasks.push(task);
|
||||
} else {
|
||||
// Maybe it's not a task name, but a task URL?
|
||||
if let Some(list) = sourceset.tasks_for(&t) {
|
||||
// Hopefully safe unwrap (unless there's a source without a corresponding task?)
|
||||
let task_list = list.iter().map(|t_name| Task::from_path(&basedir.join(&t_name), context).unwrap());
|
||||
let task_list = list.iter().map(|t_name| Task::from_path(&basedir.join(&t_name), select.clone(), context).unwrap());
|
||||
tasks.extend(task_list);
|
||||
} else {
|
||||
return Err(MissingTask(t));
|
||||
|
@ -281,6 +371,19 @@ pub fn from_dir_and_list(basedir: &Path, list: Vec<String>, context: &log::Conte
|
|||
Ok(tasks)
|
||||
}
|
||||
|
||||
/// 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, select: Select, context: &log::Context) -> Vec<Task> {
|
||||
basedir.read_dir().unwrap().filter_map(|f| {
|
||||
if f.is_err() {
|
||||
// Dismiss individual file errors
|
||||
return None;
|
||||
}
|
||||
return Task::from_path(&f.unwrap().path(), select.clone(), context);
|
||||
}).collect()
|
||||
}
|
||||
|
||||
|
||||
/// 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.
|
||||
|
|
Loading…
Reference in New Issue