use ltrait::{
Action, Source,
color_eyre::eyre::Context as _,
tokio_stream::{self, StreamExt as _},
};
use serde::Deserialize;
use std::{
os::unix::process::CommandExt as _,
path::PathBuf,
process::{Command, Stdio},
};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum TaskError {
#[error("Could not find config directory")]
ConfigDir,
#[error("failed to read task config file: {0}")]
ReadTaskFile(#[source] std::io::Error),
#[error("failed to parse the toml: {0}")]
Toml(#[source] toml::de::Error),
}
#[derive(Debug, Clone)]
pub struct TaskConfig {
pub path: Vec<PathBuf>,
}
pub fn default_path() -> Result<PathBuf, TaskError> {
Ok(dirs::config_dir()
.ok_or(TaskError::ConfigDir)?
.join("yurf")
.join("task.toml"))
}
#[derive(Debug, Clone)]
pub struct Task {
config: TaskConfig,
}
#[derive(Deserialize, Debug, Clone)]
pub struct TaskItem {
pub name: String,
pub show_if: Option<String>,
pub command: String,
}
#[derive(Deserialize, Debug)]
struct TaskFile {
task: Vec<TaskItem>,
}
impl Task {
pub fn new(config: TaskConfig) -> Self {
Self { config }
}
pub fn create_source(&self) -> Result<Source<TaskItem>, TaskError> {
let tasks = self
.config
.path
.clone()
.into_iter()
.map(|p| Ok(p))
.map(|p| p.and_then(|p| std::fs::read_to_string(p).map_err(TaskError::ReadTaskFile)))
.map(|p| p.and_then(|p| toml::from_str::<TaskFile>(&p).map_err(TaskError::Toml)))
.map(|p| p.map(|t| t.task))
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.flatten();
let aiter = tokio_stream::iter(tasks).filter(|c| {
let cmd = c.show_if.as_ref();
if let Some(cmd) = cmd {
Command::new("sh")
.arg("-c")
.arg(&cmd)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.process_group(0)
.output()
.map(|o| o.status.code() == Some(0))
.unwrap_or(false)
} else {
true
}
});
Ok(Box::pin(aiter))
}
}
impl Action for Task {
type Context = TaskItem;
fn act(&self, ctx: &Self::Context) -> ltrait::color_eyre::eyre::Result<()> {
Command::new("sh")
.arg("-c")
.arg(&ctx.command)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.process_group(0)
.spawn()
.wrap_err("failed to start the selected app")?;
Ok(())
}
}