pub fn concat_paths(a: &str, b: &str) -> String {
let mut s = String::with_capacity(a.len() + b.len() + 1);
s.push_str(a);
if !a.ends_with('/') && !b.starts_with('/') {
s.push('/');
}
s.push_str(b);
s
}
pub fn canonical_path(path: &str) -> String {
std::fs::canonicalize(path)
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_else(|_| path.to_string())
}
pub fn expand_pattern_paths(pattern: &str) -> Vec<String> {
if !pattern.contains('*') && !pattern.contains('?') && !pattern.contains('[') {
return vec![pattern.to_string()];
}
let bash_cmd = format!(
"shopt -s nullglob dotglob; for p in {}; do echo \"$$p\"; done",
pattern
);
if let Ok(output) = std::process::Command::new("bash")
.arg("-c")
.arg(&bash_cmd)
.output()
{
if output.status.success() {
let stdout_str = String::from_utf8_lossy(&output.stdout);
let paths: Vec<String> = stdout_str
.lines()
.map(|line| line.trim().to_string())
.filter(|line| !line.is_empty())
.collect();
if !paths.is_empty() {
return paths;
}
}
}
vec![pattern.to_string()]
}
pub fn wildcard_match(s: &str, pat: &str) -> bool {
let s_bytes = s.as_bytes();
let pat_bytes = pat.as_bytes();
let mut s_idx = 0;
let mut pat_idx = 0;
let mut star_idx = None;
let mut s_temp_idx = 0;
while s_idx < s_bytes.len() {
if pat_idx < pat_bytes.len()
&& (pat_bytes[pat_idx] == b'?' || pat_bytes[pat_idx] == s_bytes[s_idx])
{
s_idx += 1;
pat_idx += 1;
} else if pat_idx < pat_bytes.len() && pat_bytes[pat_idx] == b'*' {
star_idx = Some(pat_idx);
s_temp_idx = s_idx;
pat_idx += 1;
} else if let Some(star) = star_idx {
pat_idx = star + 1;
s_temp_idx += 1;
s_idx = s_temp_idx;
} else {
return false;
}
}
while pat_idx < pat_bytes.len() && pat_bytes[pat_idx] == b'*' {
pat_idx += 1;
}
pat_idx == pat_bytes.len()
}
pub fn path_matches_pattern(full_path: &str, pattern: &str) -> bool {
let clean_pattern = pattern.replace(".*", "*");
if wildcard_match(full_path, &clean_pattern) {
return true;
}
if clean_pattern.ends_with('/') {
let child_pat = format!("{}*", clean_pattern);
if wildcard_match(full_path, &child_pat) {
return true;
}
} else {
let child_pat = format!("{}/*", clean_pattern);
if wildcard_match(full_path, &child_pat) {
return true;
}
}
false
}
pub fn path_is_excluded(full_path: &str, excludes: &[String]) -> bool {
excludes
.iter()
.any(|ex| path_matches_pattern(full_path, ex))
}
pub fn path_is_encrypted(full_path: &str, enc_list: &[String]) -> bool {
enc_list
.iter()
.any(|pat| path_matches_pattern(full_path, pat))
}
pub fn path_has_valid_prefix(full_path: &str, valid_prefixes: &[String]) -> bool {
valid_prefixes
.iter()
.any(|prefix| path_matches_pattern(full_path, prefix))
}
pub fn normalize_path(path: &str) -> String {
let mut parts = Vec::new();
for component in path.split('/') {
if component == "." || component.is_empty() {
continue;
}
if component == ".." {
parts.pop();
} else {
parts.push(component);
}
}
let mut normalized = String::new();
if path.starts_with('/') {
normalized.push('/');
}
normalized.push_str(&parts.join("/"));
normalized
}