#[cfg(test)]
#[path = "cli_test.rs"]
mod cli_test;
use clap::ArgAction;
use colored::{ColoredString, Colorize};
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::{env, fmt};
use crate::args::ArgsContext;
use crate::mom_file_paths::{GlobalMomFilePath, MomFilePaths, PathIterator, SingleMomFilePath};
use crate::mom_files::MomFile;
use crate::mom_files_container::MomFilesContainer;
use crate::print_utils::MomOutput;
use crate::types::DynErrResult;
const HELP: &str = "For documentation check https://github.com/adrianmrit/mom.";
struct TaskSubcommand {
pub(crate) task: String,
pub(crate) args_context: ArgsContext,
}
#[derive(Deserialize, Serialize)]
pub(crate) enum Version {
#[serde(rename = "1")]
V1,
}
#[derive(Debug, PartialEq, Eq)]
enum ArgsError {
MissingTaskArg,
}
impl fmt::Display for ArgsError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ArgsError::MissingTaskArg => write!(f, "No task was given."),
}
}
}
impl Error for ArgsError {
fn description(&self) -> &str {
match *self {
ArgsError::MissingTaskArg => "no task given",
}
}
fn cause(&self) -> Option<&dyn Error> {
None
}
}
fn colorize_task_name(val: &str) -> ColoredString {
val.bright_cyan()
}
fn colorize_mom_file_path(val: &str) -> ColoredString {
val.bright_blue()
}
struct Mom {
mom_files: MomFilesContainer,
}
impl Mom {
fn new() -> Self {
Self {
mom_files: MomFilesContainer::new(),
}
}
fn get_mom_file_lock(&mut self, path: PathBuf) -> DynErrResult<Arc<Mutex<MomFile>>> {
let mom_file_ptr = match self.mom_files.read_mom_file(path.clone()) {
Ok(val) => val,
Err(e) => {
let e = format!("{}:\n{}", &path.to_string_lossy().red(), e);
return Err(e.into());
}
};
Ok(mom_file_ptr)
}
fn print_tasks_list(&mut self, paths: PathIterator) -> DynErrResult<()> {
let mut found = false;
for path in paths {
found = true;
let mom_file_ptr = self.get_mom_file_lock(path.clone())?;
let mom_file_lock = mom_file_ptr.lock().unwrap();
println!("{}:", colorize_mom_file_path(&path.to_string_lossy()));
let mut task_names = mom_file_lock.get_public_task_names();
task_names.sort();
if task_names.is_empty() {
println!(" {}", "No tasks found.".red());
} else {
for task in task_names {
println!(" - {}", colorize_task_name(task));
}
}
}
if !found {
println!("No mom files found.");
}
Ok(())
}
fn print_task_info(&mut self, paths: PathIterator, task: &str) -> DynErrResult<()> {
for path in paths {
let mom_file_ptr = self.get_mom_file_lock(path.clone())?;
let mom_file_lock = mom_file_ptr.lock().unwrap();
let task = mom_file_lock.clone_task(task);
match task {
Some(task) => {
println!("{}:", colorize_mom_file_path(&path.to_string_lossy()));
print!(" - {}", colorize_task_name(task.get_name()));
if task.is_private() {
print!(" {}", "(private)".red());
}
println!();
let prefix = " ";
match task.get_help().trim() {
"" => println!("{}{}", prefix, "No help to display".yellow()),
help => {
let help_lines: Vec<&str> = help.lines().collect();
println!(
"{}{}",
prefix,
help_lines.join(&format!("\n{}", prefix)).green()
)
}
}
return Ok(());
}
None => continue,
}
}
Err(format!("Task {} not found", task).into())
}
fn run_task(
&mut self,
paths: PathIterator,
task: &str,
args: &ArgsContext,
dry_run: bool,
) -> DynErrResult<()> {
for path in paths {
let mom_file_ptr = self.get_mom_file_lock(path.clone())?;
let mom_file_lock = mom_file_ptr.lock().unwrap();
let task = mom_file_lock.clone_public_task(task);
match task {
Some(task) => {
println!("{}", &path.to_string_lossy().mom_info());
return match task.run(args, &mom_file_lock, dry_run) {
Ok(val) => Ok(val),
Err(e) => {
let e = format!("{}:\n{}", &path.to_string_lossy().red(), e);
Err(e.into())
}
};
}
None => continue,
}
}
Err(format!("Task {} not found", task).into())
}
}
impl TaskSubcommand {
pub(crate) fn new(args: &clap::ArgMatches) -> Result<TaskSubcommand, ArgsError> {
let (task_name, task_args) = match args.subcommand() {
None => return Err(ArgsError::MissingTaskArg),
Some(command) => command,
};
Ok(TaskSubcommand {
task: String::from(task_name),
args_context: ArgsContext::from(task_args.clone()),
})
}
}
pub fn exec() -> DynErrResult<()> {
let app = clap::Command::new(clap::crate_name!())
.version(clap::crate_version!())
.about(clap::crate_description!())
.author(clap::crate_authors!())
.after_help(HELP)
.allow_external_subcommands(true)
.arg(
clap::Arg::new("list")
.short('l')
.long("list")
.help("Lists configuration files that can be reached from the current directory")
.action(ArgAction::SetTrue),
)
.arg(
clap::Arg::new("list-tasks")
.short('t')
.long("list-tasks")
.help("Lists tasks")
.conflicts_with_all(["task-info"])
.action(ArgAction::SetTrue),
)
.arg(
clap::Arg::new("task-info")
.short('i')
.long("task-info")
.action(ArgAction::Set)
.help("Displays information about the given task")
.value_name("TASK"),
)
.arg(
clap::Arg::new("dry")
.long("dry")
.action(ArgAction::SetTrue)
.help("Runs the task in dry mode, i.e. without executing any commands"),
)
.arg(
clap::Arg::new("file")
.short('f')
.long("file")
.action(ArgAction::Set)
.help("Search for tasks in the given file")
.value_name("FILE"),
)
.arg(
clap::Arg::new("global")
.short('g')
.long("global")
.help("Search for tasks in ~/mom/mom.global.{yml,yaml}")
.conflicts_with_all(["file"])
.action(ArgAction::SetTrue),
);
let matches = app.get_matches();
let current_dir = env::current_dir()?;
let mut mom = Mom::new();
let mom_file_paths: PathIterator = match matches.get_one::<String>("file") {
None => match matches.get_one::<bool>("global").cloned().unwrap_or(false) {
true => GlobalMomFilePath::new(),
false => MomFilePaths::new(¤t_dir),
},
Some(file_path) => SingleMomFilePath::new(file_path),
};
let dry_run = matches.get_one::<bool>("dry").cloned().unwrap_or(false);
if matches
.get_one::<bool>("list-tasks")
.cloned()
.unwrap_or(false)
{
mom.print_tasks_list(mom_file_paths)?;
return Ok(());
};
if let Some(task_name) = matches.get_one::<String>("task-info") {
mom.print_task_info(mom_file_paths, task_name)?;
return Ok(());
};
if matches.get_one::<bool>("list").cloned().unwrap_or(false) {
for path in mom_file_paths {
println!("{}", colorize_mom_file_path(&path.to_string_lossy()));
}
return Ok(());
}
let task_command = TaskSubcommand::new(&matches)?;
mom.run_task(
mom_file_paths,
&task_command.task,
&task_command.args_context,
dry_run,
)
}