zsh/compsys/ported/
shared.rs1use std::path::Path;
5pub fn is_executable(path: &Path) -> bool {
7 #[cfg(unix)]
8 {
9 use std::os::unix::fs::PermissionsExt;
10 if let Ok(meta) = path.metadata() {
11 let mode = meta.permissions().mode();
12 return mode & 0o111 != 0;
13 }
14 }
15 #[cfg(not(unix))]
16 {
17 if let Some(ext) = path.extension() {
18 let ext = ext.to_string_lossy().to_lowercase();
19 return matches!(ext.as_str(), "exe" | "bat" | "cmd" | "com");
20 }
21 }
22 false
23}
24
25pub fn glob_matches(pattern: &str, text: &str) -> bool {
30 if let Some(rest) = pattern.strip_prefix('(') {
34 if let Some(close) = find_top_close_paren(rest) {
35 let group = &rest[..close];
36 let after = &rest[close + 1..];
37 return group.split('|').any(|alt| {
38 let combined = format!("{}{}", alt, after);
39 glob_matches(&combined, text)
40 });
41 }
42 }
43 let pat: Vec<char> = pattern.chars().collect();
44 let txt: Vec<char> = text.chars().collect();
45 glob_helper(&pat, &txt)
46}
47
48fn find_top_close_paren(s: &str) -> Option<usize> {
49 let mut depth: i32 = 1;
50 for (i, c) in s.char_indices() {
51 match c {
52 '(' => depth += 1,
53 ')' => {
54 depth -= 1;
55 if depth == 0 {
56 return Some(i);
57 }
58 }
59 _ => {}
60 }
61 }
62 None
63}
64
65fn glob_helper(pat: &[char], txt: &[char]) -> bool {
66 if pat.is_empty() {
67 return txt.is_empty();
68 }
69 if pat[0] == '(' {
72 let rest: String = pat[1..].iter().collect();
73 let txt_str: String = txt.iter().collect();
74 if let Some(close) = find_top_close_paren(&rest) {
75 let group = &rest[..close];
76 let after = &rest[close + 1..];
77 return group.split('|').any(|alt| {
78 let combined = format!("{}{}", alt, after);
79 glob_matches(&combined, &txt_str)
80 });
81 }
82 }
83 match pat[0] {
84 '*' => {
85 for i in 0..=txt.len() {
86 if glob_helper(&pat[1..], &txt[i..]) {
87 return true;
88 }
89 }
90 false
91 }
92 '?' => !txt.is_empty() && glob_helper(&pat[1..], &txt[1..]),
93 c => !txt.is_empty() && txt[0] == c && glob_helper(&pat[1..], &txt[1..]),
94 }
95}
96
97pub fn glob_match(pattern: &str, text: &str) -> bool {
104 glob_matches(pattern, text)
105}
106
107pub fn edit_distance(a: &str, b: &str) -> usize {
112 let a_chars: Vec<char> = a.chars().collect();
113 let b_chars: Vec<char> = b.chars().collect();
114 let m = a_chars.len();
115 let n = b_chars.len();
116
117 let mut dp = vec![vec![0; n + 1]; m + 1];
118
119 #[allow(clippy::needless_range_loop)]
122 for i in 0..=m {
123 dp[i][0] = i;
124 }
125 #[allow(clippy::needless_range_loop)]
126 for j in 0..=n {
127 dp[0][j] = j;
128 }
129
130 for i in 1..=m {
131 for j in 1..=n {
132 let cost = if a_chars[i - 1] == b_chars[j - 1] {
133 0
134 } else {
135 1
136 };
137 dp[i][j] = (dp[i - 1][j] + 1)
138 .min(dp[i][j - 1] + 1)
139 .min(dp[i - 1][j - 1] + cost);
140 }
141 }
142
143 dp[m][n]
144}
145
146pub fn is_ignored(s: &str, patterns: &[String]) -> bool {
150 for pattern in patterns {
151 if glob_match(pattern, s) {
152 return true;
153 }
154 }
155 false
156}
157
158pub fn get_ignored_patterns(context: &str) -> Vec<String> {
162 crate::ported::modules::zutil::lookupstyle(context, "ignored-patterns")
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
174 fn test_glob_match_simple() {
175 assert!(glob_match("*.txt", "file.txt"));
176 assert!(glob_match("*.txt", ".txt"));
177 assert!(!glob_match("*.txt", "file.rs"));
178 }
179
180 #[test]
181 fn test_glob_match_question() {
182 assert!(glob_match("file?.txt", "file1.txt"));
183 assert!(glob_match("file?.txt", "fileX.txt"));
184 assert!(!glob_match("file?.txt", "file.txt"));
185 assert!(!glob_match("file?.txt", "file12.txt"));
186 }
187
188 #[test]
189 fn test_glob_match_star_middle() {
190 assert!(glob_match("foo*bar", "foobar"));
191 assert!(glob_match("foo*bar", "foo123bar"));
192 assert!(glob_match("foo*bar", "fooXYZbar"));
193 assert!(!glob_match("foo*bar", "foobaz"));
194 }
195
196 #[test]
197 fn test_glob_match_multiple_stars() {
198 assert!(glob_match("*foo*", "foo"));
199 assert!(glob_match("*foo*", "afoo"));
200 assert!(glob_match("*foo*", "foob"));
201 assert!(glob_match("*foo*", "afoob"));
202 assert!(!glob_match("*foo*", "bar"));
203 }
204
205 #[test]
206 fn test_glob_match_exact() {
207 assert!(glob_match("exact", "exact"));
208 assert!(!glob_match("exact", "exacty"));
209 assert!(!glob_match("exact", "xact"));
210 }
211}