Skip to main content

sericom_core/path_utils/
validators.rs

1use crate::path_utils::ExpandPaths;
2use std::path::PathBuf;
3
4/// Validates a directory
5///
6/// Expands path and checks if it exists and is a directory.
7///
8/// Used in `sericom`s `clap` cli interface to validate user input and
9/// propogate errors before running the program.
10///
11/// # Errors
12/// Errors if path-expansion fails or if the path doesn't exist or is not a dir.
13pub fn validate_dir(input: &str) -> Result<Option<PathBuf>, String> {
14    let p = PathBuf::from(input)
15        .get_expanded_path()
16        .ok_or("Error expanding path.")?;
17    if !p.exists() || !p.is_dir() {
18        return Err(format!(
19            "Invalid directory '{input}' out-dir\nEither does not exist or is not a directory"
20        ));
21    }
22    Ok(Some(p))
23}
24
25/// Validates a script
26///
27/// Expands path and checks if it exists and is a file. Then checks whether
28/// the file is executable.
29///
30/// ## Unix
31/// On Unix this checks whether there are any executable bits in the file's
32/// permissions (i.e. '-rwxrw--x')
33///
34/// ## Windows
35/// On Windows this just checks whether the file ends in '.exe', '.bat',
36/// '.cmd', '.com', or '.ps1'.
37///
38/// Used in `sericom`s `clap` cli interface to validate user input and
39/// propogate errors before running the program.
40///
41/// # Errors
42/// Errors if path-expansion fails, if the path doesn't exist, is not a file,
43/// or is not executable according to the definitions in [Unix](##Unix) and [Windows](##Windows)
44/// respectively.
45pub fn is_script(input: &str) -> Result<Option<PathBuf>, String> {
46    let p = PathBuf::from(input)
47        .get_expanded_path()
48        .ok_or("Error expanding path.")?;
49
50    if !p.exists() || !p.is_file() {
51        return Err(format!(
52            "Invalid file '{input}'\nEither does not exist or is not a file"
53        ));
54    }
55
56    if !is_executable(&p) {
57        return Err(format!(
58            "Invalid file type '{}'\nMake sure the file is executable",
59            p.display()
60        ));
61    }
62
63    Ok(Some(p))
64}
65
66pub(crate) fn is_executable(path: &std::path::Path) -> bool {
67    #[cfg(unix)]
68    {
69        use std::{fs::metadata, os::unix::fs::MetadataExt};
70
71        let Ok(script) = metadata(path) else {
72            return false;
73        };
74        let mode = script.mode();
75        mode & 0o111 != 0
76    }
77
78    #[cfg(windows)]
79    {
80        match path.extension() {
81            Some(ext) => matches!(
82                ext.to_ascii_lowercase()
83                    .to_str()
84                    .expect("Converted to ascii"),
85                "exe" | "bat" | "cmd" | "com" | "ps1"
86            ),
87            None => false,
88        }
89    }
90}