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}