Skip to main content

wrkflw_utils/
lib.rs

1// utils crate
2
3use std::path::Path;
4
5pub fn is_workflow_file(path: &Path) -> bool {
6    // First, check for GitLab CI files by name
7    if let Some(file_name) = path.file_name() {
8        let file_name_str = file_name.to_string_lossy().to_lowercase();
9        if file_name_str == ".gitlab-ci.yml" || file_name_str.ends_with("gitlab-ci.yml") {
10            return true;
11        }
12    }
13
14    // Then check for GitHub Actions workflows
15    if let Some(ext) = path.extension() {
16        if ext == "yml" || ext == "yaml" {
17            // Check if the file is in a .github/workflows directory
18            if let Some(parent) = path.parent() {
19                return parent.ends_with(".github/workflows") || parent.ends_with("workflows");
20            } else {
21                // Check if filename contains workflow indicators
22                let filename = path
23                    .file_name()
24                    .map(|f| f.to_string_lossy().to_lowercase())
25                    .unwrap_or_default();
26
27                return filename.contains("workflow")
28                    || filename.contains("action")
29                    || filename.contains("ci")
30                    || filename.contains("cd");
31            }
32        }
33    }
34    false
35}
36
37/// Module for safely handling file descriptor redirection
38///
39/// On Unix systems (Linux, macOS), this module provides true file descriptor
40/// redirection by duplicating stderr and redirecting it to /dev/null.
41///
42/// On Windows systems, the redirection functionality is limited due to platform
43/// differences in file descriptor handling. The functions will execute without
44/// error but stderr may not be fully suppressed.
45pub mod fd {
46    use std::io::Result;
47
48    /// Represents a redirected stderr that can be restored
49    pub struct RedirectedStderr {
50        #[cfg(unix)]
51        original_fd: Option<std::os::unix::io::RawFd>,
52        #[cfg(unix)]
53        null_fd: Option<std::os::unix::io::RawFd>,
54        #[cfg(windows)]
55        _phantom: std::marker::PhantomData<()>,
56    }
57
58    #[cfg(unix)]
59    mod unix_impl {
60        use super::*;
61        use nix::fcntl::{open, OFlag};
62        use nix::sys::stat::Mode;
63        use nix::unistd::{close, dup, dup2};
64        use std::io;
65        use std::os::unix::io::RawFd;
66        use std::path::Path;
67
68        /// Standard file descriptors
69        const STDERR_FILENO: RawFd = 2;
70
71        impl RedirectedStderr {
72            /// Creates a new RedirectedStderr that redirects stderr to /dev/null
73            pub fn to_null() -> Result<Self> {
74                // Duplicate the current stderr fd
75                let stderr_backup = match dup(STDERR_FILENO) {
76                    Ok(fd) => fd,
77                    Err(e) => return Err(io::Error::other(e)),
78                };
79
80                // Open /dev/null
81                let null_fd = match open(Path::new("/dev/null"), OFlag::O_WRONLY, Mode::empty()) {
82                    Ok(fd) => fd,
83                    Err(e) => {
84                        let _ = close(stderr_backup); // Clean up on error
85                        return Err(io::Error::other(e));
86                    }
87                };
88
89                // Redirect stderr to /dev/null
90                if let Err(e) = dup2(null_fd, STDERR_FILENO) {
91                    let _ = close(stderr_backup); // Clean up on error
92                    let _ = close(null_fd);
93                    return Err(io::Error::other(e));
94                }
95
96                Ok(RedirectedStderr {
97                    original_fd: Some(stderr_backup),
98                    null_fd: Some(null_fd),
99                })
100            }
101        }
102
103        impl Drop for RedirectedStderr {
104            /// Automatically restores stderr when the RedirectedStderr is dropped
105            fn drop(&mut self) {
106                if let Some(orig_fd) = self.original_fd.take() {
107                    // Restore the original stderr
108                    let _ = dup2(orig_fd, STDERR_FILENO);
109                    let _ = close(orig_fd);
110                }
111
112                // Close the null fd
113                if let Some(null_fd) = self.null_fd.take() {
114                    let _ = close(null_fd);
115                }
116            }
117        }
118    }
119
120    #[cfg(windows)]
121    mod windows_impl {
122        use super::*;
123
124        impl RedirectedStderr {
125            /// Creates a new RedirectedStderr that redirects stderr to NUL on Windows
126            pub fn to_null() -> Result<Self> {
127                // On Windows, we can't easily redirect stderr at the file descriptor level
128                // like we can on Unix systems. This is a simplified implementation that
129                // doesn't actually redirect but provides the same interface.
130                // The actual stderr suppression will need to be handled differently on Windows.
131                Ok(RedirectedStderr {
132                    _phantom: std::marker::PhantomData,
133                })
134            }
135        }
136
137        impl Drop for RedirectedStderr {
138            /// No-op drop implementation for Windows
139            fn drop(&mut self) {
140                // Nothing to restore on Windows in this simplified implementation
141            }
142        }
143    }
144
145    /// Run a function with stderr redirected to /dev/null (Unix) or suppressed (Windows), then restore stderr
146    ///
147    /// # Platform Support
148    /// - **Unix (Linux, macOS)**: Fully supported - stderr is redirected to /dev/null
149    /// - **Windows**: Limited support - function executes but stderr may be visible
150    ///
151    /// # Example
152    /// ```
153    /// use wrkflw_utils::fd::with_stderr_to_null;
154    ///
155    /// let result = with_stderr_to_null(|| {
156    ///     eprintln!("This will be hidden on Unix");
157    ///     42
158    /// }).unwrap();
159    /// assert_eq!(result, 42);
160    /// ```
161    pub fn with_stderr_to_null<F, T>(f: F) -> Result<T>
162    where
163        F: FnOnce() -> T,
164    {
165        #[cfg(unix)]
166        {
167            let _redirected = RedirectedStderr::to_null()?;
168            Ok(f())
169        }
170        #[cfg(windows)]
171        {
172            // On Windows, we can't easily redirect stderr at the FD level,
173            // so we just run the function without redirection.
174            // This means stderr won't be suppressed on Windows, but the function will work.
175            Ok(f())
176        }
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183
184    #[test]
185    fn test_fd_redirection() {
186        // This test will write to stderr, which should be redirected on Unix
187        // On Windows, it will just run normally without redirection
188        let result = fd::with_stderr_to_null(|| {
189            // This would normally appear in stderr (suppressed on Unix, visible on Windows)
190            eprintln!("This should be redirected to /dev/null on Unix");
191            // Return a test value to verify the function passes through the result
192            42
193        });
194
195        // The function should succeed and return our test value on both platforms
196        assert!(result.is_ok());
197        assert_eq!(result.unwrap(), 42);
198    }
199}