use std::borrow::Cow;
use ignore::gitignore::GitignoreBuilder;
pub fn is_negated_glob(pattern: &str) -> bool {
let mut chars = pattern.chars();
let first_char = chars.next();
let second_char = chars.next();
first_char == Some('!') && second_char != Some('(')
}
pub fn non_negated_glob(pattern: &str) -> &str {
if is_negated_glob(pattern) { &pattern[1..] } else { pattern }
}
pub fn is_pattern(pattern: &str) -> bool {
if pattern.starts_with('!') {
return true;
}
let mut was_last_escape = false;
for c in pattern.chars() {
if !was_last_escape && matches!(c, '*' | '{' | '?' | '[') {
return true;
}
was_last_escape = c == '\\' && !was_last_escape;
}
false
}
pub fn pattern_names_dir(pattern: &str, dir_name: &str) -> bool {
if !is_pattern(pattern) {
return unescape_glob_text(pattern) == dir_name;
}
if dir_name.contains('/') || dir_name.contains('\\') {
return false;
}
let mut builder = GitignoreBuilder::new("");
if builder.add_line(None, pattern).is_err() {
return false;
}
match builder.build() {
Ok(gitignore) => gitignore.matched(dir_name, true).is_ignore(),
Err(_) => false,
}
}
pub fn escape_glob_text(text: &str) -> String {
let mut result = String::with_capacity(text.len());
for c in text.chars() {
if matches!(c, '\\' | '*' | '{' | '}' | '?' | '[' | ']' | '!') {
result.push('\\');
}
result.push(c);
}
result
}
pub fn escape_glob_text_for_cli(text: &str) -> String {
let mut result = String::with_capacity(text.len());
for c in text.chars() {
match c {
'[' | ']' | '{' | '}' | '*' | '?' => {
result.push('[');
result.push(c);
result.push(']');
}
_ => result.push(c),
}
}
result
}
pub fn unescape_glob_text(text: &str) -> Cow<'_, str> {
if !text.contains('\\') {
return Cow::Borrowed(text);
}
let mut result = String::with_capacity(text.len());
let mut chars = text.chars();
while let Some(c) = chars.next() {
if c == '\\' {
match chars.next() {
Some(next) => result.push(next),
None => result.push(c),
}
} else {
result.push(c);
}
}
Cow::Owned(result)
}
pub fn is_absolute_pattern(pattern: &str) -> bool {
let pattern = if is_negated_glob(pattern) { &pattern[1..] } else { pattern };
pattern.starts_with('/') || is_windows_absolute_pattern(pattern)
}
fn is_windows_absolute_pattern(pattern: &str) -> bool {
let mut chars = pattern.chars();
let next_char = chars.next();
if next_char.is_none() || !next_char.unwrap().is_ascii_alphabetic() {
return false;
}
let mut next_char = chars.next();
while next_char.is_some() && next_char.unwrap().is_ascii_alphabetic() {
next_char = chars.next();
}
if next_char != Some(':') {
return false;
}
let next_char = chars.next();
matches!(next_char, Some('/'))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_escape_and_unescape_glob_text() {
assert_eq!(escape_glob_text("routes/[id].svelte"), "routes/\\[id\\].svelte");
assert_eq!(escape_glob_text("{{myfile}}.yaml"), "\\{\\{myfile\\}\\}.yaml");
assert_eq!(escape_glob_text("a*b?c!d\\e"), "a\\*b\\?c\\!d\\\\e");
assert_eq!(escape_glob_text("plain/file.txt"), "plain/file.txt");
assert_eq!(escape_glob_text_for_cli("/[app]/dir"), "/[[]app[]]/dir");
assert_eq!(escape_glob_text_for_cli("/plain/dir"), "/plain/dir");
assert_eq!(unescape_glob_text("routes/\\[id\\].svelte"), "routes/[id].svelte");
assert_eq!(unescape_glob_text("plain/file.txt"), "plain/file.txt");
assert_eq!(unescape_glob_text("a\\\\b"), "a\\b");
assert_eq!(unescape_glob_text("a\\"), "a\\");
assert!(is_pattern("routes/[id].svelte"));
assert!(!is_pattern(&escape_glob_text("routes/[id].svelte")));
assert_eq!(unescape_glob_text(&escape_glob_text("a*b?c!d\\e[]{}")), "a*b?c!d\\e[]{}");
assert!(is_pattern("a\\\\*b"));
assert_eq!(unescape_glob_text("a\\\\*b"), "a\\*b");
assert!(!is_pattern("a\\*b"));
assert_eq!(unescape_glob_text("a\\*b"), "a*b");
}
#[test]
fn should_get_if_pattern_names_dir() {
assert!(pattern_names_dir("dist", "dist"));
assert!(!pattern_names_dir("dist", "other"));
assert!(pattern_names_dir("su*", "sub"));
assert!(!pattern_names_dir("su*", "other"));
assert!(pattern_names_dir("?ub", "sub"));
assert!(pattern_names_dir("[sd]ist", "dist"));
assert!(!pattern_names_dir("[sd]ist", "list"));
assert!(pattern_names_dir("*", "anything"));
assert!(pattern_names_dir("*.min.js", "a.min.js"));
assert!(pattern_names_dir("\\[id\\]", "[id]"));
assert!(pattern_names_dir("[[]id[]]", "[id]"));
assert!(!pattern_names_dir("dist", "dist/sub"));
assert!(!pattern_names_dir("*", "dist/sub"));
}
#[test]
fn should_get_if_absolute_pattern() {
assert_eq!(is_absolute_pattern("test.ts"), false);
assert_eq!(is_absolute_pattern("!test.ts"), false);
assert_eq!(is_absolute_pattern("/test.ts"), true);
assert_eq!(is_absolute_pattern("!/test.ts"), true);
assert_eq!(is_absolute_pattern("D:/test.ts"), true);
assert_eq!(is_absolute_pattern("!D:/test.ts"), true);
}
}