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}