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}