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")]
|
#[structopt(short = "b", long = "basedir")]
|
||||||
pub basedir: Option<String>,
|
pub basedir: Option<String>,
|
||||||
|
|
||||||
|
#[structopt(long = "inbox")]
|
||||||
|
pub inbox: bool,
|
||||||
|
|
||||||
|
#[structopt(long = "inbox-folder")]
|
||||||
|
pub inboxdir: Option<String>,
|
||||||
|
|
||||||
//#[structopt(def)]
|
//#[structopt(def)]
|
||||||
pub tasks: Vec<String>,
|
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::env::{set_current_dir as cd, set_var};
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
mod cli;
|
mod cli;
|
||||||
mod db;
|
mod db;
|
||||||
|
@ -7,6 +8,7 @@ mod log;
|
||||||
mod task;
|
mod task;
|
||||||
|
|
||||||
use log::Context;
|
use log::Context;
|
||||||
|
use task::Select;
|
||||||
|
|
||||||
fn main() -> Result<(), std::io::Error> {
|
fn main() -> Result<(), std::io::Error> {
|
||||||
let mut context = Context::new();
|
let mut context = Context::new();
|
||||||
|
@ -16,20 +18,30 @@ fn main() -> Result<(), std::io::Error> {
|
||||||
|
|
||||||
set_var("GITBUILDDIR", &basedir);
|
set_var("GITBUILDDIR", &basedir);
|
||||||
|
|
||||||
let mut tasks = if cmd.tasks.is_empty() {
|
if (cmd.force && cmd.inbox) || (cmd.inbox && cmd.inboxdir.is_some()) || (cmd.force && cmd.inboxdir.is_some()) {
|
||||||
log::info("no_task", &context);
|
println!("CONFLICTING COMMANDS: You can only run --inbox, --inbox-dir or --forge. These options cannot be combined");
|
||||||
task::from_dir(&basedir, &context).expect("Could not load DB")
|
exit(2);
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 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::backend::{Repo, backend};
|
||||||
use crate::log;
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct Task {
|
pub struct Task {
|
||||||
|
/// The filename for the task
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
/// Full path to the task
|
||||||
pub bin: PathBuf,
|
pub bin: PathBuf,
|
||||||
|
/// Potentially, a source repository to track for updates
|
||||||
pub source: Option<String>,
|
pub source: Option<String>,
|
||||||
|
/// The full Repo information
|
||||||
pub repo: Option<Repo>,
|
pub repo: Option<Repo>,
|
||||||
|
/// Variables for task config
|
||||||
pub config: HashMap<String, String>,
|
pub config: HashMap<String, String>,
|
||||||
|
/// Potentially, a branch/commit to track
|
||||||
pub branch: Option<String>,
|
pub branch: Option<String>,
|
||||||
|
/// List of hosts on which this task should run
|
||||||
pub hosts: Vec<String>,
|
pub hosts: Vec<String>,
|
||||||
|
/// Whether the source, if any, has been cloned already
|
||||||
pub cloned: bool,
|
pub cloned: bool,
|
||||||
|
/// Whether to track subrepository/submodule updates
|
||||||
pub subupdates: bool,
|
pub subupdates: bool,
|
||||||
|
/// The context in which to store variables for translations
|
||||||
pub context: log::Context,
|
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
|
/// config returns an option of (settings directory, ignored tasks) as
|
||||||
|
@ -48,7 +145,7 @@ pub fn config(basedir: &Path) -> (PathBuf, Vec<String>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Task {
|
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();
|
let name = path.file_name().unwrap().to_str().unwrap().to_string();
|
||||||
|
|
||||||
// We don't return a task if:
|
// We don't return a task if:
|
||||||
|
@ -103,6 +200,7 @@ impl Task {
|
||||||
cloned,
|
cloned,
|
||||||
subupdates: read_extension(path, "subupdates").is_some(),
|
subupdates: read_extension(path, "subupdates").is_some(),
|
||||||
context,
|
context,
|
||||||
|
select,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,6 +251,9 @@ impl Task {
|
||||||
|
|
||||||
self.info("run");
|
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?
|
let cmd_out = Command::new("bash") // TODO: no need to call bash?
|
||||||
.arg(&self.bin)
|
.arg(&self.bin)
|
||||||
.arg(&self.name)
|
.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);
|
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.
|
/// 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
|
/// 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.
|
/// 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> {
|
pub fn from_dir_and_list(basedir: &Path, list: Vec<String>, select: Select, context: &log::Context) -> Result<Vec<Task>, MissingTask> {
|
||||||
// Safe unwrap because we checked that basedir existed before
|
// TODO: Write tests for permissions problems
|
||||||
// or maybe can crash for permission problem? TODO: write tests
|
|
||||||
|
// 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 sourceset = SourceSet::from(basedir).unwrap();
|
||||||
let mut tasks = Vec::new();
|
let mut tasks = Vec::new();
|
||||||
for t in list {
|
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);
|
tasks.push(task);
|
||||||
} else {
|
} else {
|
||||||
// Maybe it's not a task name, but a task URL?
|
// Maybe it's not a task name, but a task URL?
|
||||||
if let Some(list) = sourceset.tasks_for(&t) {
|
if let Some(list) = sourceset.tasks_for(&t) {
|
||||||
// Hopefully safe unwrap (unless there's a source without a corresponding task?)
|
// 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);
|
tasks.extend(task_list);
|
||||||
} else {
|
} else {
|
||||||
return Err(MissingTask(t));
|
return Err(MissingTask(t));
|
||||||
|
@ -281,6 +371,19 @@ pub fn from_dir_and_list(basedir: &Path, list: Vec<String>, context: &log::Conte
|
||||||
Ok(tasks)
|
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
|
/// Takes a &Path to a basedir, and a &str task_name, and return
|
||||||
/// the corresponding source directory as a PathBuf. Does not check
|
/// the corresponding source directory as a PathBuf. Does not check
|
||||||
/// if the target exists.
|
/// if the target exists.
|
||||||
|
|
Loading…
Reference in New Issue