sbatch_rs/sbatch/
mod.rs

1//! This module provides a builder for the `sbatch` command in Slurm.
2
3use std::collections::BTreeSet;
4use thiserror::Error;
5
6use crate::{SbatchOption, SbatchOptionError};
7
8/// sbatch command builder
9///
10/// # Examples
11///
12/// ```
13/// use sbatch_rs::{Sbatch, SbatchOption};
14///
15/// // Create a new `Sbatch` instance
16/// let sbatch = Sbatch::new()
17///     .add_option(SbatchOption::JobName("test".to_string())).unwrap()
18///     .add_option(SbatchOption::Output("test.out".to_string())).unwrap()
19///     .add_option(SbatchOption::Error("test.err".to_string())).unwrap()
20///     .set_script("test.sh".to_string()).unwrap()
21///     .build();
22///
23/// // Verify that the `sbatch` command was built properly
24/// assert!(sbatch.is_ok());
25/// assert_eq!(sbatch.unwrap(), "sbatch --error=test.err --job-name=test --output=test.out test.sh");
26/// ```
27#[derive(Debug, Clone)]
28pub struct Sbatch {
29    sbatch_options: Option<BTreeSet<SbatchOption>>,
30    script: Option<String>,
31}
32
33/// The `SbatchError` enum represents an error that can occur when building an `sbatch` command.
34///
35/// Errors include:
36/// - No options or script provided
37/// - Script is empty
38/// - Sbatch option error
39#[derive(Debug, Error)]
40pub enum SbatchError {
41    #[error("No sbatch options or script provided")]
42    NoOptionsOrScript,
43    #[error("Script is empty")]
44    ScriptEmpty,
45    #[error("Sbatch option error: {0}")]
46    SbatchOptionError(#[from] SbatchOptionError),
47    #[error("Execution failed: {0}")]
48    SbatchExecutionError(String),
49}
50
51impl Sbatch {
52    /// Creates a new `Sbatch` instance.
53    ///
54    /// # Examples
55    ///
56    /// ```
57    /// use sbatch_rs::Sbatch;
58    ///
59    /// // Create a new `Sbatch` instance
60    /// let sbatch = Sbatch::new();
61    /// ```
62    pub fn new() -> Self {
63        Sbatch {
64            sbatch_options: None,
65            script: None,
66        }
67    }
68
69    /// Adds an `SbatchOption` to the `Sbatch` instance.
70    ///
71    /// # Arguments
72    ///
73    /// * `option` - An `SbatchOption` to add to the `Sbatch` instance.
74    ///
75    /// # Returns
76    ///
77    /// This function returns a mutable reference to the `Sbatch` instance.
78    ///
79    /// # Errors
80    ///
81    /// This function returns a `SbatchError` if the `SbatchOption` is invalid.
82    ///
83    /// # Examples
84    ///
85    /// ```
86    /// use sbatch_rs::{Sbatch, SbatchOption};
87    ///
88    /// // Create a new `Sbatch` instance
89    /// let sbatch = Sbatch::new()
90    ///     .add_option(SbatchOption::JobName("test".to_string())).unwrap()
91    ///     .add_option(SbatchOption::Output("test.out".to_string())).unwrap()
92    ///     .add_option(SbatchOption::Error("test.err".to_string())).unwrap()
93    ///     .add_option(SbatchOption::Wrap("test".to_string())).unwrap()
94    ///     .build();
95    ///
96    /// // Verify that the `sbatch` command was built properly
97    /// assert!(sbatch.is_ok());
98    /// assert_eq!(sbatch.unwrap(), "sbatch --error=test.err --job-name=test --output=test.out --wrap=\"test\"");
99    /// ```
100    pub fn add_option(&mut self, option: SbatchOption) -> Result<&mut Self, SbatchError> {
101        // Validate the option
102        option.validate()?;
103
104        // Add the option to the set
105        self.sbatch_options
106            .get_or_insert_with(BTreeSet::new)
107            .insert(option);
108        Ok(self)
109    }
110
111    /// Sets the script for the `Sbatch` instance.
112    ///
113    /// # Arguments
114    ///
115    /// * `script` - A string representing the script to run.
116    ///
117    /// # Returns
118    ///
119    /// This function returns a mutable reference to the `Sbatch` instance.
120    ///
121    /// # Errors
122    ///
123    /// This function returns a `SbatchError` if the script is empty.
124    ///
125    /// # Examples
126    ///
127    /// ```
128    /// use sbatch_rs::Sbatch;
129    ///
130    /// // Create a new `Sbatch` instance
131    /// let sbatch = Sbatch::new()
132    ///     .set_script("test.sh".to_string()).unwrap()
133    ///     .build();
134    ///
135    /// // Verify that the `sbatch` command was built properly
136    /// assert!(sbatch.is_ok());
137    /// assert_eq!(sbatch.unwrap(), "sbatch test.sh");
138    /// ```
139    pub fn set_script(&mut self, script: String) -> Result<&mut Self, SbatchError> {
140        let script = script.trim().to_string();
141        if script.is_empty() {
142            Err(SbatchError::ScriptEmpty)
143        } else {
144            self.script = Some(script);
145            Ok(self)
146        }
147    }
148
149    /// Builds the `sbatch` command.
150    ///
151    /// # Returns
152    ///
153    /// This function returns a string representing the `sbatch` command.
154    ///
155    /// # Errors
156    ///
157    /// This function returns a `SbatchError` if no options or script are provided.
158    ///
159    /// # Examples
160    ///
161    /// ```
162    /// use sbatch_rs::{Sbatch, SbatchOption};
163    ///
164    /// // Create a new `Sbatch` instance
165    /// let sbatch = Sbatch::new()
166    ///     .add_option(SbatchOption::JobName("test".to_string())).unwrap()
167    ///     .add_option(SbatchOption::Output("test.out".to_string())).unwrap()
168    ///     .add_option(SbatchOption::Error("test.err".to_string())).unwrap()
169    ///     .set_script("test.sh".to_string()).unwrap()
170    ///     .build();
171    ///     
172    /// // Verify that the `sbatch` command was built properly
173    /// assert!(sbatch.is_ok());
174    /// assert_eq!(sbatch.unwrap(), "sbatch --error=test.err --job-name=test --output=test.out test.sh");
175    pub fn build(&self) -> Result<String, SbatchError> {
176        // Convert the sbatch options to a space-joined string
177        let options: Option<String> = self.sbatch_options.as_ref().map(|options| {
178            options
179                .iter()
180                .map(|o| o.to_string())
181                .collect::<Vec<String>>()
182                .join(" ")
183        });
184
185        // Combine the options and script
186        match (options, &self.script) {
187            (Some(o), Some(s)) => Ok(format!("sbatch {o} {s}")),
188            (Some(o), None) => Ok(format!("sbatch {o}")),
189            (None, Some(s)) => Ok(format!("sbatch {s}")),
190            (None, None) => Err(SbatchError::NoOptionsOrScript),
191        }
192    }
193}
194
195impl Default for Sbatch {
196    /// Creates a default `Sbatch` instance.
197    ///
198    /// # Examples
199    ///
200    /// ```
201    /// use sbatch_rs::Sbatch;
202    ///
203    /// // Create a default `Sbatch` instance
204    /// let sbatch = Sbatch::default();
205    /// ```
206    fn default() -> Self {
207        Self::new()
208    }
209}