use {
bobr::multiplexer::Multiplexer,
notify::{RecommendedWatcher, Watcher},
signal_hook::{
consts::{SIGINT, SIGTERM},
iterator::Signals,
},
std::path::Path,
};
pub mod args;
pub mod compiler;
pub mod exec;
pub mod plan;
pub mod reference;
pub mod workflow;
use {
crate::{compiler::Compiler, workflow::Workflow},
anyhow::Result,
args::{ManualFormat, WorkflowInitOutput},
exec::{ExecutionEngine, OutputMode},
std::path::PathBuf,
};
#[tokio::main]
async fn main() -> Result<()> {
let cmd = crate::args::ClapArgumentLoader::load()?;
match cmd.command {
| crate::args::Command::Manual { path, format } => {
let out_path = PathBuf::from(path);
std::fs::create_dir_all(&out_path)?;
match format {
| ManualFormat::Manpages => {
reference::build_manpages(&out_path)?;
},
| ManualFormat::Markdown => {
reference::build_markdown(&out_path)?;
},
}
Ok(())
},
| crate::args::Command::Autocomplete { path, shell } => {
let out_path = PathBuf::from(path);
std::fs::create_dir_all(&out_path)?;
reference::build_shell_completion(&out_path, &shell)?;
Ok(())
},
| crate::args::Command::Workflow(wf) => match wf {
| crate::args::WorkflowCommand::Schema => {
print!(
"{}",
serde_json::to_string_pretty(&schemars::schema_for!(crate::workflow::Workflow)).unwrap()
);
Ok(())
},
| crate::args::WorkflowCommand::Init { template, output } => {
match output {
| WorkflowInitOutput::File(f) => std::fs::write(f, template.render())?,
| WorkflowInitOutput::Stdout => print!("{}", template.render()),
};
Ok(())
},
},
| crate::args::Command::Execute {
plan,
workers,
no_stdout,
no_stderr,
} => {
let exec_engine = ExecutionEngine::new(OutputMode {
stdout: !no_stdout,
stderr: !no_stderr,
});
exec_engine.execute(&plan, workers)?;
Ok(())
},
| crate::args::Command::Plan {
workflow,
nodes,
args,
format,
} => {
let w = Workflow::load(&workflow)?;
let nodes = nodes.select(&w)?;
let c = Compiler::new(w);
let x = c.plan(&nodes, &args)?;
print!("{}", format.serialize(&x)?);
Ok(())
},
| crate::args::Command::List { workflow, format } => {
let w = Workflow::load(&workflow)?;
let c = Compiler::new(w);
c.list(&format).await?;
Ok(())
},
| crate::args::Command::Describe {
workflow,
nodes,
format,
} => {
let w = Workflow::load(&workflow)?;
let nodes = nodes.select(&w)?;
let c = Compiler::new(w);
c.describe(&nodes, &format).await?;
Ok(())
},
| crate::args::Command::Multiplex {
commands,
program,
stderr,
stdout,
parallelism,
} => {
let parallelism = parallelism.unwrap_or(commands.len());
let result = bobr::multiplexer::Multiplexer::new(program, stderr, commands, parallelism)
.run()
.await?;
if let Some(v) = stdout {
println!("{}", v.serialize(&result).unwrap());
}
Ok(())
},
| crate::args::Command::Watch {
commands,
filter,
parallelism,
program,
root,
stderr,
} => {
let regex = fancy_regex::Regex::new(&filter)?;
let trim_path =
std::fs::canonicalize(&root).unwrap().to_str().unwrap().to_owned() + std::path::MAIN_SEPARATOR_STR;
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<String>();
tokio::spawn(async move {
let program = program.clone();
let commands = commands.clone();
while let Some(_) = rx.recv().await {
Multiplexer::new(program.clone(), stderr, commands.clone(), parallelism.unwrap_or(1))
.run()
.await
.unwrap();
}
});
let mut watcher = RecommendedWatcher::new(
move |result: Result<notify::Event, notify::Error>| match result {
| Ok(e) => {
let event_kind = match &e.kind {
| notify::EventKind::Create(v) => match &v {
| notify::event::CreateKind::Any => "created/any",
| notify::event::CreateKind::File => "created/file",
| notify::event::CreateKind::Folder => "created/folder",
| notify::event::CreateKind::Other => "created/other",
},
| notify::EventKind::Modify(v) => match &v {
| notify::event::ModifyKind::Any => "modified/any",
| notify::event::ModifyKind::Data(v) => match &v {
| notify::event::DataChange::Any => "modified/data/any",
| notify::event::DataChange::Size => "modified/data/size",
| notify::event::DataChange::Content => "modified/data/content",
| notify::event::DataChange::Other => "modified/data/other",
},
| notify::event::ModifyKind::Metadata(v) => match &v {
| notify::event::MetadataKind::Any => "modified/metadata/any",
| notify::event::MetadataKind::AccessTime => "modified/metadata/accesstime",
| notify::event::MetadataKind::WriteTime => "modified/metadata/writetime",
| notify::event::MetadataKind::Permissions => "modified/metadata/permissions",
| notify::event::MetadataKind::Ownership => "modified/metadata/ownership",
| notify::event::MetadataKind::Extended => "modified/metadata/extended",
| notify::event::MetadataKind::Other => "modified/metadata/other",
},
| notify::event::ModifyKind::Name(v) => match &v {
| notify::event::RenameMode::Any => "modified/name/any",
| notify::event::RenameMode::To => "modified/name/to",
| notify::event::RenameMode::From => "modified/name/from",
| notify::event::RenameMode::Both => "modified/name/both",
| notify::event::RenameMode::Other => "modified/name/other",
},
| notify::event::ModifyKind::Other => "modified/other",
},
| notify::EventKind::Remove(v) => match &v {
| notify::event::RemoveKind::Any => "removed/any",
| notify::event::RemoveKind::File => "removed/file",
| notify::event::RemoveKind::Folder => "removed/folder",
| notify::event::RemoveKind::Other => "removed/other",
},
| notify::EventKind::Other => "other",
| notify::EventKind::Any => "any",
| notify::EventKind::Access(k) => match &k {
| notify::event::AccessKind::Any => "access/any",
| notify::event::AccessKind::Read => "access/read",
| notify::event::AccessKind::Open(v) => match &v {
| notify::event::AccessMode::Any => "access/open/any",
| notify::event::AccessMode::Execute => "access/open/execute",
| notify::event::AccessMode::Read => "access/open/read",
| notify::event::AccessMode::Write => "access/open/write",
| notify::event::AccessMode::Other => "access/open/other",
},
| notify::event::AccessKind::Close(v) => match &v {
| notify::event::AccessMode::Any => "access/close/any",
| notify::event::AccessMode::Execute => "access/close/execute",
| notify::event::AccessMode::Read => "access/close/read",
| notify::event::AccessMode::Write => "access/close/write",
| notify::event::AccessMode::Other => "access/close/other",
},
| notify::event::AccessKind::Other => "other",
},
};
let event_path = e.paths[0].to_str().unwrap().trim_start_matches(&trim_path);
let filter = format!("{}|{}", &event_kind, &event_path);
if regex.is_match(&filter).unwrap() {
tx.send(filter).unwrap();
}
},
| Err(e) => {
println!("{:?}", e);
},
},
notify::Config::default(),
)?;
watcher.watch(Path::new(&root), notify::RecursiveMode::Recursive)?;
Signals::new([SIGINT, SIGTERM]).unwrap().wait();
Ok(())
},
}
}
#[cfg(test)]
pub mod test {
use {crate::Workflow, anyhow::Result};
const WF_MIN_YAML: &str = include_str!("../res/templates/min.neomake.yaml");
const WF_MAX_YAML: &str = include_str!("../res/templates/max.neomake.yaml");
const WF_PYTHON_YAML: &str = include_str!("../res/templates/python.neomake.yaml");
const WF_TEST_YAML: &str = include_str!("../test/neomake.yaml");
const WF_MY_YAML: &str = include_str!("../neomake.yaml");
const ALL_WF_YAMLS: &[&str] = &[WF_MIN_YAML, WF_MAX_YAML, WF_PYTHON_YAML, WF_TEST_YAML, WF_MY_YAML];
#[tokio::test]
pub async fn test_parse_workflow() -> Result<()> {
for wf in ALL_WF_YAMLS {
let _ = serde_yaml::from_str::<Workflow>(wf)?;
}
Ok(())
}
}