use alloc::format;
use alloc::string::{String, ToString};
#[derive(Debug, Clone)]
pub(crate) struct Exclude {
repository: Option<String>,
globs: globset::GlobSet,
}
impl Exclude {
pub(crate) fn new(pattern: &str) -> Result<Self, crate::Error> {
let invalid = |reason: String| crate::Error::InvalidExclude {
pattern: pattern.to_string(),
reason,
};
let (repository, glob) = match pattern.split_once(':') {
Some((repository, glob)) if crate::organization_source::is_repository(repository) => {
(Some(repository.to_ascii_lowercase()), glob)
}
_ => (None, pattern),
};
if glob.starts_with('!') {
return Err(invalid("negates, which is not supported".to_string()));
}
let directory = glob.ends_with('/');
let body = glob.trim_end_matches('/');
let anchored = body.contains('/');
let body = body.trim_start_matches('/');
if body.is_empty() {
return Err(invalid("names no path".to_string()));
}
let base = if anchored {
body.to_string()
} else {
format!("**/{body}")
};
let mut set = globset::GlobSetBuilder::new();
let contents = format!("{base}/**");
let variants: &[&str] = if directory {
&[&contents]
} else {
&[&base, &contents]
};
for variant in variants {
set.add(
globset::GlobBuilder::new(variant)
.literal_separator(true)
.backslash_escape(true)
.build()
.map_err(|err| invalid(err.kind().to_string()))?,
);
}
Ok(Self {
repository,
globs: set.build().map_err(|err| invalid(err.to_string()))?,
})
}
}
pub(crate) fn excluded(excludes: &[Exclude], organization: bool, root: &str, path: &str) -> bool {
if excludes.is_empty() {
return false;
}
let relative = path
.strip_prefix(root)
.unwrap_or(path)
.trim_start_matches(['/', '\\'])
.replace('\\', "/");
let (repository, relative) = if organization {
let mut parts = relative.splitn(3, '/');
let owner = parts.next().unwrap_or_default();
let name = parts.next().unwrap_or_default();
(
Some(format!("{owner}/{name}")),
parts.next().unwrap_or_default(),
)
} else {
(None, relative.as_str())
};
excludes.iter().any(|exclude| {
exclude.repository.as_deref().is_none_or(|limited| {
repository
.as_deref()
.is_some_and(|name| name.eq_ignore_ascii_case(limited))
}) && exclude.globs.is_match(relative)
})
}