tcrm_monitor/monitor/
config.rs

1//! Task configuration types and shell definitions.
2//!
3//! This module provides configuration types for tasks, including dependency management
4//! and cross-platform shell support.
5
6use std::collections::HashMap;
7
8use tcrm_task::tasks::config::TaskConfig;
9
10/// Type alias for a collection of named task specifications.
11///
12/// Maps task names to their corresponding [`TaskSpec`] configurations.
13/// Used as the primary input for [`TaskMonitor`](crate::monitor::tasks::TaskMonitor).
14///
15/// # Examples
16///
17/// ```rust
18/// use std::collections::HashMap;
19/// use tcrm_monitor::monitor::config::{TcrmTasks, TaskSpec, TaskShell};
20/// use tcrm_task::tasks::config::TaskConfig;
21///
22/// let mut tasks: TcrmTasks = HashMap::new();
23/// tasks.insert(
24///     "build".to_string(),
25///     TaskSpec::new(TaskConfig::new("cargo").args(["build"]))
26///         .shell(TaskShell::Auto)
27/// );
28/// ```
29pub type TcrmTasks = HashMap<String, TaskSpec>;
30
31/// Task specification with dependency management and execution configuration.
32///
33/// Wraps a [`TaskConfig`] with additional metadata for dependency management,
34/// shell selection, and execution behavior in a task graph.
35///
36/// # Default Values
37/// - `config`: Default-initialized [`TaskConfig`]
38/// - `shell`: `Some(TaskShell::None)`
39/// - `dependencies`: `None`
40/// - `terminate_after_dependents_finished`: `Some(false)`
41/// - `ignore_dependencies_error`: `Some(false)`
42///
43/// ## Examples
44///
45/// ### Simple Task
46///
47/// ```rust
48/// use tcrm_monitor::monitor::config::{TaskSpec, TaskShell};
49/// use tcrm_task::tasks::config::TaskConfig;
50///
51/// let task = TaskSpec::new(
52///     TaskConfig::new("echo").args(["Hello, World!"])
53/// );
54/// ```
55///
56/// ### Task with Dependencies
57///
58/// ```rust
59/// use tcrm_monitor::monitor::config::{TaskSpec, TaskShell};
60/// use tcrm_task::tasks::config::TaskConfig;
61///
62/// let build_task = TaskSpec::new(
63///     TaskConfig::new("cargo").args(["build", "--release"])
64/// )
65/// .dependencies(["test", "lint"])
66/// .shell(TaskShell::Auto)
67/// .terminate_after_dependents(true);
68/// ```
69///
70/// ### Task with Custom Configuration
71///
72/// ```rust
73/// use tcrm_monitor::monitor::config::{TaskSpec, TaskShell};
74/// use tcrm_task::tasks::config::TaskConfig;
75///
76/// let server_task = TaskSpec::new(
77///     TaskConfig::new("node")
78///         .args(["server.js"])
79///         .working_dir("/app")
80///         .enable_stdin(true)
81///         .timeout_ms(0) // No timeout
82/// )
83/// .shell(TaskShell::Auto)
84/// .ignore_dependencies_error(true);
85/// ```
86#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
87#[derive(Debug, Clone)]
88pub struct TaskSpec {
89    /// The underlying task configuration containing command, arguments, and execution options.
90    ///
91    /// **Default:** `TaskConfig::default()`
92    pub config: TaskConfig,
93
94    /// Shell to use for task execution. `None` means direct execution without shell.
95    ///
96    /// **Default:** `Some(TaskShell::None)`
97    pub shell: Option<TaskShell>,
98
99    /// List of task names this task depends on. Must complete before this task starts.
100    ///
101    /// **Default:** `None`
102    pub dependencies: Option<Vec<String>>,
103
104    /// Whether to terminate this task when all dependent tasks finish.
105    /// Useful for long-running services that should stop when their dependents complete.
106    ///
107    /// **Default:** `Some(false)`
108    pub terminate_after_dependents_finished: Option<bool>,
109
110    /// Whether to ignore errors from dependency tasks and continue execution anyway.
111    ///
112    /// **Default:** `Some(false)`
113    pub ignore_dependencies_error: Option<bool>,
114}
115
116impl Default for TaskSpec {
117    fn default() -> Self {
118        Self {
119            config: TaskConfig::default(),
120            shell: Some(TaskShell::None),
121            dependencies: None,
122            terminate_after_dependents_finished: Some(false),
123            ignore_dependencies_error: Some(false),
124        }
125    }
126}
127
128impl TaskSpec {
129    /// Creates a new task specification from a task configuration.
130    ///
131    /// # Arguments
132    ///
133    /// * `config` - The underlying task configuration
134    ///
135    /// # Examples
136    ///
137    /// ```rust
138    /// use tcrm_monitor::monitor::config::TaskSpec;
139    /// use tcrm_task::tasks::config::TaskConfig;
140    ///
141    /// let task = TaskSpec::new(
142    ///     TaskConfig::new("ls").args(["-la"])
143    /// );
144    /// ```
145    #[must_use]
146    pub fn new(config: TaskConfig) -> Self {
147        TaskSpec {
148            config,
149            ..Default::default()
150        }
151    }
152
153    /// Sets the shell for task execution.
154    ///
155    /// # Arguments
156    ///
157    /// * `shell` - The shell type to use
158    ///
159    /// # Examples
160    ///
161    /// ```rust
162    /// use tcrm_monitor::monitor::config::{TaskSpec, TaskShell};
163    /// use tcrm_task::tasks::config::TaskConfig;
164    ///
165    /// let task = TaskSpec::new(TaskConfig::new("echo").args(["test"]))
166    ///     .shell(TaskShell::Auto);
167    /// ```
168    #[must_use]
169    pub fn shell(mut self, shell: TaskShell) -> Self {
170        self.shell = Some(shell);
171        self
172    }
173
174    /// Adds dependencies to this task.
175    ///
176    /// Dependencies must complete successfully before this task can start.
177    ///
178    /// # Arguments
179    ///
180    /// * `dependencies` - An iterator of task names this task depends on
181    ///
182    /// # Examples
183    ///
184    /// ```rust
185    /// use tcrm_monitor::monitor::config::TaskSpec;
186    /// use tcrm_task::tasks::config::TaskConfig;
187    ///
188    /// let task = TaskSpec::new(TaskConfig::new("cargo").args(["build"]))
189    ///     .dependencies(["test", "lint"]);
190    /// ```
191    #[must_use]
192    pub fn dependencies<I, S>(mut self, dependencies: I) -> Self
193    where
194        I: IntoIterator<Item = S>,
195        S: Into<String>,
196    {
197        self.dependencies = Some(dependencies.into_iter().map(Into::into).collect());
198        self
199    }
200
201    /// Sets whether to terminate this task when its dependents complete.
202    ///
203    /// Useful for long-running services that should stop when their dependents finish.
204    ///
205    /// # Arguments
206    ///
207    /// * `terminate` - Whether to terminate after dependents complete
208    ///
209    /// # Examples
210    ///
211    /// ```rust
212    /// use tcrm_monitor::monitor::config::TaskSpec;
213    /// use tcrm_task::tasks::config::TaskConfig;
214    ///
215    /// // A database server that should stop when tests finish
216    /// let db_task = TaskSpec::new(TaskConfig::new("postgres").args(["-D", "/data"]))
217    ///     .terminate_after_dependents(true);
218    /// ```
219    #[must_use]
220    pub fn terminate_after_dependents(mut self, terminate: bool) -> Self {
221        self.terminate_after_dependents_finished = Some(terminate);
222        self
223    }
224
225    /// Sets whether to ignore errors from dependency tasks.
226    ///
227    /// When true, this task will run even if its dependencies fail.
228    ///
229    /// # Arguments
230    ///
231    /// * `ignore` - Whether to ignore dependency errors
232    ///
233    /// # Examples
234    ///
235    /// ```rust
236    /// use tcrm_monitor::monitor::config::TaskSpec;
237    /// use tcrm_task::tasks::config::TaskConfig;
238    ///
239    /// // Cleanup task that should run regardless of test results
240    /// let cleanup = TaskSpec::new(TaskConfig::new("rm").args(["-rf", "/tmp/test"]))
241    ///     .dependencies(["test"])
242    ///     .ignore_dependencies_error(true);
243    /// ```
244    #[must_use]
245    pub fn ignore_dependencies_error(mut self, ignore: bool) -> Self {
246        self.ignore_dependencies_error = Some(ignore);
247        self
248    }
249}
250
251/// Shell type for task execution.
252///
253/// Supports multiple shell types across Unix and Windows platforms with automatic detection.
254///
255/// ## Platform Support
256///
257/// - **Unix**: Bash, Sh, Zsh, Fish
258/// - **Windows**: Cmd, Powershell
259/// - **Cross-platform**: Auto (detects the best available shell), None (direct execution)
260///
261/// ## Examples
262///
263/// ```rust
264/// use tcrm_monitor::monitor::config::TaskShell;
265///
266/// // Use automatic shell detection
267/// let auto_shell = TaskShell::Auto;
268///
269/// // Execute without shell
270/// let direct = TaskShell::None;
271///
272/// // Platform-specific shells
273/// #[cfg(unix)]
274/// let bash_shell = TaskShell::Bash;
275///
276/// #[cfg(windows)]
277/// let powershell = TaskShell::Powershell;
278/// ```
279#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
280#[derive(Debug, Clone, PartialEq)]
281#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
282pub enum TaskShell {
283    /// Execute command directly without shell
284    None,
285    /// Automatic shell detection based on platform and availability
286    Auto,
287    /// Windows Command Prompt (Windows only)
288    #[cfg(windows)]
289    Cmd,
290    /// Windows `PowerShell` (Windows only)
291    #[cfg(windows)]
292    Powershell,
293    /// Bourne Again Shell (Unix only)
294    #[cfg(unix)]
295    Bash,
296    /// POSIX Shell (Unix only)
297    #[cfg(unix)]
298    Sh,
299    /// Z Shell (Unix only)
300    #[cfg(unix)]
301    Zsh,
302    /// Fish Shell (Unix only)
303    #[cfg(unix)]
304    Fish,
305}
306
307impl Default for TaskShell {
308    fn default() -> Self {
309        Self::None
310    }
311}
312
313impl TaskShell {
314    /// Returns true if this shell is available on Unix systems.
315    ///
316    /// # Examples
317    ///
318    /// ```rust
319    /// use tcrm_monitor::monitor::config::TaskShell;
320    ///
321    /// assert!(TaskShell::Auto.is_cross_platform());
322    /// assert!(TaskShell::None.is_cross_platform());
323    ///
324    /// #[cfg(unix)]
325    /// {
326    ///     assert!(TaskShell::Bash.is_unix());
327    ///     assert!(TaskShell::Zsh.is_unix());
328    /// }
329    ///
330    /// #[cfg(windows)]
331    /// {
332    ///     assert!(!TaskShell::Cmd.is_unix());
333    /// }
334    /// ```
335    #[must_use]
336    pub fn is_unix(&self) -> bool {
337        match self {
338            #[cfg(unix)]
339            TaskShell::Bash | TaskShell::Sh | TaskShell::Zsh | TaskShell::Fish => true,
340            _ => false,
341        }
342    }
343
344    /// Returns true if this shell is available on Windows systems.
345    ///
346    /// # Examples
347    ///
348    /// ```rust
349    /// use tcrm_monitor::monitor::config::TaskShell;
350    ///
351    /// #[cfg(windows)]
352    /// {
353    ///     assert!(TaskShell::Powershell.is_windows());
354    ///     assert!(TaskShell::Cmd.is_windows());
355    /// }
356    ///
357    /// #[cfg(unix)]
358    /// {
359    ///     assert!(!TaskShell::Bash.is_windows());
360    /// }
361    /// ```
362    #[must_use]
363    pub fn is_windows(&self) -> bool {
364        match self {
365            #[cfg(windows)]
366            TaskShell::Powershell | TaskShell::Cmd => true,
367            _ => false,
368        }
369    }
370
371    /// Returns true if this shell is available on all platforms.
372    ///
373    /// # Examples
374    ///
375    /// ```rust
376    /// use tcrm_monitor::monitor::config::TaskShell;
377    ///
378    /// assert!(TaskShell::Auto.is_cross_platform());
379    /// assert!(TaskShell::None.is_cross_platform());
380    /// ```
381    #[must_use]
382    pub fn is_cross_platform(&self) -> bool {
383        matches!(self, TaskShell::Auto | TaskShell::None)
384    }
385
386    /// Returns the command name used to invoke this shell, if applicable.
387    ///
388    /// # Examples
389    ///
390    /// ```rust
391    /// use tcrm_monitor::monitor::config::TaskShell;
392    ///
393    /// assert_eq!(TaskShell::Auto.command_name(), None);
394    /// assert_eq!(TaskShell::None.command_name(), None);
395    ///
396    /// #[cfg(unix)]
397    /// assert_eq!(TaskShell::Bash.command_name(), Some("bash"));
398    ///
399    /// #[cfg(windows)]
400    /// assert_eq!(TaskShell::Powershell.command_name(), Some("powershell"));
401    /// ```
402    #[must_use]
403    pub fn command_name(&self) -> Option<&str> {
404        match self {
405            TaskShell::None | TaskShell::Auto => None,
406            #[cfg(windows)]
407            TaskShell::Cmd => Some("cmd"),
408            #[cfg(windows)]
409            TaskShell::Powershell => Some("powershell"),
410            #[cfg(unix)]
411            TaskShell::Bash => Some("bash"),
412            #[cfg(unix)]
413            TaskShell::Sh => Some("sh"),
414            #[cfg(unix)]
415            TaskShell::Zsh => Some("zsh"),
416            #[cfg(unix)]
417            TaskShell::Fish => Some("fish"),
418        }
419    }
420}