Skip to main content

modelcards/
utils.rs

1//! # `utils` Module
2//!
3//! Provides utility functions for file operations and path manipulations.
4//!
5//! ## Functions
6//!
7//! - `strip_unc`: Removes the UNC prefix from a Windows path.
8//! - `create_file`: Creates a file with the specified content.
9//! - `load_json_file`: Loads and deserializes a JSON file into a [`serde_json::Value`].
10//! - `is_directory_empty`: Check if a directory is empty.
11//!
12//! ## Notes
13//!
14//! - The `strip_unc` function is specific to Windows systems and deals with paths starting with `\\?\`.
15//! - The `create_file` and `load_json_file` functions use [`anyhow::Result`] for error handling, allowing for simple and flexible error management.
16
17use std::{
18    fs::File,
19    io::{Read, Write},
20    path::Path
21};
22
23use anyhow::{bail, Context, Result};
24
25/// const will be used to remove the network part of the UNC to display users a more common path on windows systems.
26const LOCAL_UNC: &str = "\\\\?\\";
27
28/// Removes the UNC prefix from a Windows path.
29/// 
30/// The `strip_unc` function is specific to Windows systems and deals with paths starting with `\\?\`.
31/// Canonicalize(path) function on windows system returns a path with UNC.
32/// UNC sample: `\\?\C:\Users\VssAdministrator\AppData\Local\Temp\new_project`
33/// More details on Universal Naming Convention (UNC) can be found [here](https://en.wikipedia.org/wiki/Path_(computing)#Uniform_Naming_Convention).
34/// 
35/// <div class="alert alert-warning">
36/// This is a workaround until this issue https://github.com/rust-lang/rust/issues/42869 was fixed.
37/// </div>
38/// 
39/// ## Example
40/// 
41/// ```rust
42/// use std::path::Path;
43/// use modelcards::utils::strip_unc;
44///
45/// let path = Path::new(r"\\?\C:\Path\to\File");
46/// let cleaned_path = strip_unc(path);
47/// ```
48/// 
49pub fn strip_unc(path: &Path) -> String {
50    let path_to_refine = path.to_str().unwrap_or("");
51    if path_to_refine.is_empty() {
52        path.to_string_lossy().trim_start_matches(LOCAL_UNC).to_string()
53    } else {
54        path_to_refine.trim_start_matches(LOCAL_UNC).to_string()
55    }
56}
57
58/// Creates a file with the specified content.
59/// 
60/// ## Panics
61/// 
62/// This function uses [`anyhow::Result`] for error handling, allowing for simple and flexible error management.
63/// Errors can occur when creating the file or writing the content. Errors will be of type [`anyhow::Error`].
64/// 
65/// ## Example
66/// 
67/// ```rust
68/// use std::path::Path;
69/// use modelcards::utils::create_file;
70///
71/// let path = Path::new("your_path.txt");
72/// create_file(path, "File content").expect("Failed to create file");
73/// ```
74/// 
75pub fn create_file(path: &Path, content: &str) -> Result<()> {
76    let mut file = File::create(path).with_context(|| format!("Failed to create File {}", path.display()))?;
77    file.write_all(content.as_bytes())?;
78    Ok(())
79}
80
81/// Load and deserializes a JSON file into a `serde_json::Value`.
82/// 
83/// ## Panics
84/// 
85/// This function uses `anyhow::Result` for error handling, allowing for simple and flexible error management.
86/// Errors can occur when reading the file (io error) or deserializing the content (serde error). Errors will be of type `anyhow::Error`.
87/// 
88/// ## Example
89///
90/// ```rust,no_run
91/// use std::path::Path;
92/// use modelcards::utils::load_json_file;
93///
94/// let path = Path::new("your_path.json");
95/// let json_value = load_json_file(path).expect("Failed to load JSON");
96/// ```
97///
98pub fn load_json_file(file_path: &Path) -> Result<serde_json::Value> {
99    let mut file = File::open(file_path).with_context(|| format!("Failed to open file {}", file_path.display()))?;
100        let mut file_string = String::new();
101        file.read_to_string(&mut file_string)?;
102        Ok(serde_json::from_str(&file_string)?)
103}
104
105/// Check if a directory is empty.
106/// 
107/// Utility function that checks if a directory is empty.
108/// 
109/// ## Panics
110/// 
111/// This function uses `anyhow::Result` for error handling, allowing for simple and flexible error management.
112/// Errors can occur when reading the directory (io error). Errors will be of type `anyhow::Error`.
113/// 
114/// ## Example
115///
116/// ```rust
117/// use std::path::Path;
118/// use modelcards::utils::is_directory_empty;
119///
120/// let path = Path::new(".");
121/// if is_directory_empty(path, true).expect("Failed to read directory") {
122///    println!("Directory is empty!");
123/// }
124/// ```
125/// 
126pub fn is_directory_empty(path: &Path, allow_hidden: bool) -> Result<bool> {
127    if path.is_dir() {
128        let mut entries = match path.read_dir() {
129            Ok(entries) => entries,
130            Err(e) => bail!("Could not read '{}' because of error: {}", path.to_string_lossy().to_string(), e),
131        };
132        if entries.any(|x| match x {
133            Ok(file) => {
134                if allow_hidden {
135                    !file.file_name().to_str().unwrap_or("").starts_with('.')
136                } else {
137                    true
138                }
139
140            },
141            Err(_) => true,
142        }) {
143            return Ok(false);
144        }
145        return Ok(true);
146    }
147    Ok(false)
148}
149
150/// Console utilities
151/// 
152/// Provides utility functions for logging messages to the console.
153/// Additionally provides a function to exit the program with a success or error message and appropriate exit code.
154/// Currently this module uses the [`log`] crate for logging messages to the console and is a wrapper to this,
155/// which will allow implementing different logging mechanisms (console, files, port) in the future.
156/// 
157/// ## Functions
158/// 
159/// - `error`: Logs an error message to the console.
160/// - `error_exit`: Logs an error message to the console and exits the program with an error code.
161/// - `warn`: Logs a warning message to the console.
162/// - `info`: Logs an information message to the console.
163/// - `success_exit`: Logs a success message to the console and exits the program with a success code.
164/// - `debug`: Logs a debug message to the console. Only available in debug builds.
165/// 
166/// ## Notes
167/// 
168/// - The `debug` function is only available in debug builds and will not be compiled in release builds.
169/// - All functions use the env_logger crate for logging messages to the console. Therefore visibility of messages can be controlled by setting the `RUST_LOG` environment variable.
170/// - The `error_exit` and `success_exit` functions will exit the program with an appropriate exit code (1 for error, 0 for success).
171/// 
172pub mod console {
173    //use std::io::Write;
174
175    /// Logs an error message to the console.
176    /// 
177    /// The `error` function logs an error message to the console using the [`log`] crate.
178    /// Additionally, if an error is provided, it will be logged as well.
179    /// Visibility of the message can be controlled by setting the `RUST_LOG` environment variable.
180    /// 
181    /// ## Example
182    /// 
183    /// ```rust
184    /// use modelcards::utils::console::error;
185    /// error("An error occurred", None::<String>);
186    /// ```
187    /// 
188    pub fn error(msg: &str, e: Option<impl std::fmt::Debug>) {
189        log::error!("{}", msg);
190        //eprintln!("Error: {}", msg);
191        if let Some(e) = e {
192            log::error!("{:?}", e);
193            //eprintln!("{:?}", e);
194        }
195    }
196
197    pub fn error_exit(msg: &str, e: Option<impl std::fmt::Debug>) {
198        error(msg, e);
199        std::process::exit(1);
200    }
201
202    /// Logs a warning message to the console.
203    /// 
204    /// The `warn` function logs a warning message to the console using the [`log`] crate.
205    /// Visibility of the message can be controlled by setting the `RUST_LOG` environment variable.
206    /// 
207    /// ## Example
208    /// 
209    /// ```rust
210    /// use modelcards::utils::console::warn;
211    /// warn("You should be careful with this!");
212    /// ```
213    /// 
214    pub fn warn(msg: &str) {
215        log::warn!("{}", msg);
216        //eprintln!("Warning: {}", msg);
217    }
218
219    /// Logs an information message to the console.
220    /// 
221    /// The `info` function logs an information message to the console using the [`log`] crate.
222    /// Visibility of the message can be controlled by setting the `RUST_LOG` environment variable.
223    /// 
224    /// ## Example
225    /// 
226    /// ```rust
227    /// use modelcards::utils::console::info;
228    /// info("Everything is fine!");
229    /// ```
230    /// 
231    pub fn info(msg: &str) {
232        log::info!("{}", msg);
233        //println!("{}", msg);
234    }
235
236    pub fn success_exit(msg: &str) {
237        info(msg);
238        std::process::exit(0);
239    }
240
241    /// Logs a debug message to the console.
242    /// 
243    /// The `debug` function logs a debug message to the console using the [`log`] crate.
244    /// Visibility of the message can be controlled by setting the `RUST_LOG` environment variable.
245    /// 
246    /// ## Example
247    /// 
248    /// ```rust
249    /// use modelcards::utils::console::debug;
250    /// debug("You should be careful with this!");
251    /// ```
252    /// 
253    #[cfg(debug_assertions)]
254    pub fn debug(msg: &str) {
255        log::debug!("{}", msg);
256        //println!("Debug: {}", msg);
257    }
258
259    /// Disables logging of debug messages to the console.
260    /// 
261    /// If debug_assertions are disabled, the `debug` function will be compiled out and not perform any action.
262    /// 
263    /// ## Example
264    /// 
265    /// ```rust
266    /// use modelcards::utils::console::debug;
267    /// debug("You should be careful with this!");
268    /// ```
269    /// 
270    #[cfg(not(debug_assertions))]
271    pub fn debug(_msg: &str) {}
272}
273
274
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279    use std::{
280        env::temp_dir,
281        fs::{canonicalize, create_dir, remove_dir_all},
282        path::{Path, PathBuf}
283    };
284
285    fn get_temp_dir(path: &str, create: bool) -> PathBuf {
286        let mut dir = temp_dir();
287        dir.push(path);
288        if dir.exists() {
289            remove_dir_all(&dir).expect("Could not free test directory");
290        }
291        if create {
292            create_dir(&dir).expect("Could not create test directory");
293        }
294        dir
295    }
296
297    #[test]
298    fn strip_unc_test() {
299        let dir = get_temp_dir("test_strip_unc", true);
300        if cfg!(target_os = "windows") {
301            let canonicalized = canonicalize(Path::new(&dir)).expect("Failed to canonicalize path");
302            let stripped_path = strip_unc(&canonicalized);
303            assert!(same_file::is_same_file(Path::new(&stripped_path), &dir).expect("Failed to compare files"));
304            assert!(!stripped_path.starts_with(LOCAL_UNC), "The path was not stripped.");
305        } else {
306            let canonicalized = canonicalize(Path::new(&dir)).expect("Failed to canonicalize path");
307            assert_eq!(
308                strip_unc(&canonicalized),
309                canonicalized.to_str().expect("Failed to convert path to str").to_string()
310            );
311        }
312
313        remove_dir_all(&dir).expect("Failed to remove test directory");
314    }
315
316    // If the following test fails it means that the canonicalize function is fixed and strip_unc
317    // function/workaround is not anymore required.
318    // See issue https://github.com/rust-lang/rust/issues/42869 as a reference.
319    #[test]
320    #[cfg(target_os = "windows")]
321    fn strip_unc_required_test() {
322        let dir = get_temp_dir("test_strip_unc_required", true);
323        let canonicalized_path = canonicalize(Path::new(&dir)).expect("Failed to canonicalize path");
324        assert!(same_file::is_same_file(Path::new(&canonicalized_path), &dir).expect("Failed to compare files"));
325        assert!(canonicalized_path.to_str().expect("Failed to convert path to str").starts_with(LOCAL_UNC));
326
327        remove_dir_all(&dir).expect("Failed to remove test directory");
328    }
329
330    #[test]
331    fn create_file_test() {
332        let dir = get_temp_dir("test_create_file", true);
333        let file_path = dir.join("test_file.txt");
334        create_file(&file_path, "test content").expect("Could not create file");
335        assert!(file_path.exists());
336        remove_dir_all(&dir).expect("Failed to remove test directory");
337    }
338}