ltrait_source_task/
lib.rs1use ltrait::{
2 Action, Source,
3 color_eyre::eyre::Context as _,
4 tokio_stream::{self, StreamExt as _},
5};
6use serde::Deserialize;
7use std::{
8 os::unix::process::CommandExt as _,
9 path::PathBuf,
10 process::{Command, Stdio},
11};
12use thiserror::Error;
13
14#[derive(Debug, Error)]
15pub enum TaskError {
16 #[error("Could not find config directory")]
17 ConfigDir,
18 #[error("failed to read task config file: {0}")]
19 ReadTaskFile(#[source] std::io::Error),
20 #[error("failed to parse the toml: {0}")]
21 Toml(#[source] toml::de::Error),
22}
23
24#[derive(Debug, Clone)]
25pub struct TaskConfig {
26 pub path: Vec<PathBuf>,
28}
29
30pub fn default_path() -> Result<PathBuf, TaskError> {
32 Ok(dirs::config_dir()
33 .ok_or(TaskError::ConfigDir)?
34 .join("yurf")
35 .join("task.toml"))
36}
37
38#[derive(Debug, Clone)]
39pub struct Task {
40 config: TaskConfig,
41}
42
43#[derive(Deserialize, Debug, Clone)]
44pub struct TaskItem {
45 pub name: String,
46 pub show_if: Option<String>,
51 pub command: String,
55}
56
57#[derive(Deserialize, Debug)]
58struct TaskFile {
59 task: Vec<TaskItem>,
60}
61
62impl Task {
63 pub fn new(config: TaskConfig) -> Self {
64 Self { config }
65 }
66
67 pub fn create_source(&self) -> Result<Source<TaskItem>, TaskError> {
68 let tasks = self
69 .config
70 .path
71 .clone()
72 .into_iter()
73 .map(|p| Ok(p))
74 .map(|p| p.and_then(|p| std::fs::read_to_string(p).map_err(TaskError::ReadTaskFile)))
75 .map(|p| p.and_then(|p| toml::from_str::<TaskFile>(&p).map_err(TaskError::Toml)))
76 .map(|p| p.map(|t| t.task))
77 .collect::<Result<Vec<_>, _>>()?
78 .into_iter()
79 .flatten();
80
81 let aiter = tokio_stream::iter(tasks).filter(|c| {
82 let cmd = c.show_if.as_ref();
83 if let Some(cmd) = cmd {
84 Command::new("sh")
85 .arg("-c")
86 .arg(&cmd)
87 .stdin(Stdio::null())
88 .stdout(Stdio::null())
89 .stderr(Stdio::null())
90 .process_group(0)
91 .output()
92 .map(|o| o.status.code() == Some(0))
93 .unwrap_or(false)
94 } else {
95 true
96 }
97 });
98
99 Ok(Box::pin(aiter))
100 }
101}
102
103impl Action for Task {
104 type Context = TaskItem;
105
106 fn act(&self, ctx: &Self::Context) -> ltrait::color_eyre::eyre::Result<()> {
107 Command::new("sh")
108 .arg("-c")
109 .arg(&ctx.command)
110 .stdin(Stdio::null())
111 .stdout(Stdio::null())
112 .stderr(Stdio::null())
113 .process_group(0)
114 .spawn()
115 .wrap_err("failed to start the selected app")?;
116
117 Ok(())
118 }
119}