use crate::sync;
pub use error::{Error, Result};
pub use ignore_rules::IgnoreRules;
pub use std::hash::Hash;
pub use sync::{PathSync, PathSyncSingleton};
pub use crate::notify::{make_watcher, PathEvent, RecommendedWatcher};
use std::{fmt::Debug, path::PathBuf};
use crate::error;
use crate::ignore_rules;
#[derive(Debug, Clone)]
pub enum MatchResult {
NoMatch,
Ignore,
Whitelist,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum PatternRelativity {
Anywhere,
RelativeTo {
directory: String,
},
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum PathKind {
Any,
Directory,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum PatternEffect {
Ignore,
Whitelist,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum Source {
Global,
File {
path: PathBuf,
line: usize,
},
CommandLine {
current_dir: PathBuf,
},
}
#[derive(Debug)]
pub struct Pattern {
pub glob: String,
pub original: String,
pub source: Source,
pub effect: PatternEffect,
pub relativity: PatternRelativity,
pub path_kind: PathKind,
}
impl Pattern {
pub fn new(source: Source, original: &str) -> Self {
let original = original.to_owned();
let current_dir = match &source {
Source::Global => "".to_string(),
Source::File { path, .. } => {
let path = path
.parent()
.expect("Pattern source file doesn't have parent")
.to_string_lossy()
.to_string();
if path.starts_with('/') {
path
} else {
format!("/{path}")
}
}
Source::CommandLine { current_dir } => current_dir.to_string_lossy().to_string(),
};
let begin_exclamation = original.starts_with('!');
let mut line = if begin_exclamation || original.starts_with(r"\!") {
original[1..].to_owned()
} else {
original.to_owned()
};
if !line.ends_with("\\ ") {
line = line.trim_end().to_string();
}
let end_slash = line.ends_with('/');
if end_slash {
line = line[..line.len() - 1].to_string()
}
let begin_slash = line.starts_with('/');
let non_final_slash = if !line.is_empty() {
line[..line.len() - 1].chars().any(|c| c == '/')
} else {
false
};
if begin_slash {
line = line[1..].to_string();
}
let current_dir = if current_dir.ends_with('/') {
¤t_dir[..current_dir.len() - 1]
} else {
¤t_dir
};
let effect = if begin_exclamation {
PatternEffect::Whitelist
} else {
PatternEffect::Ignore
};
let path_kind = if end_slash {
PathKind::Directory
} else {
PathKind::Any
};
let relativity = if non_final_slash {
PatternRelativity::RelativeTo {
directory: current_dir.to_owned(),
}
} else {
PatternRelativity::Anywhere
};
let glob = transform_pattern_for_glob(&line, relativity.clone(), path_kind.clone());
Pattern {
glob,
original,
source,
effect,
relativity,
path_kind,
}
}
}
fn transform_pattern_for_glob(
original: &str,
relativity: PatternRelativity,
path_kind: PathKind,
) -> String {
let anything_anywhere = |p| format!("**/{p}");
let anything_relative = |p, directory| format!("{directory}/**/{p}");
let directory_anywhere = |p| format!("**/{p}/**");
let directory_relative = |p, directory| format!("{directory}/**/{p}/**");
match (path_kind, relativity) {
(PathKind::Any, PatternRelativity::Anywhere) => anything_anywhere(original),
(PathKind::Any, PatternRelativity::RelativeTo { directory }) => {
anything_relative(original, directory)
}
(PathKind::Directory, PatternRelativity::Anywhere) => directory_anywhere(original),
(PathKind::Directory, PatternRelativity::RelativeTo { directory }) => {
directory_relative(original, directory)
}
}
}
pub fn build_pattern_list(patterns: Vec<String>, source: Source) -> Vec<Pattern> {
patterns
.iter()
.map(|p| Pattern::new(source.clone(), p))
.collect()
}