use crate::glob::split_pattern;
use crate::GlobEntry;
use bexpand::Expression;
use std::path::PathBuf;
use tracing::{event, Level};
use super::auto_source_detection::IGNORED_CONTENT_DIRS;
#[derive(Debug, Clone)]
pub struct PublicSourceEntry {
pub base: String,
pub pattern: String,
pub negated: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SourceEntry {
Auto { base: PathBuf },
Pattern { base: PathBuf, pattern: String },
Ignored { base: PathBuf, pattern: String },
External { base: PathBuf },
}
#[derive(Debug, Clone, Default)]
pub struct Sources {
sources: Vec<SourceEntry>,
}
impl Sources {
pub fn new(sources: Vec<SourceEntry>) -> Self {
Self { sources }
}
pub fn iter(&self) -> impl Iterator<Item = &SourceEntry> {
self.sources.iter()
}
}
impl PublicSourceEntry {
pub fn optimize(&mut self) {
let Ok(base) = dunce::canonicalize(&self.base) else {
event!(Level::ERROR, "Failed to resolve base: {:?}", self.base);
return;
};
self.base = base.to_string_lossy().to_string();
if !self.pattern.contains('*') {
let combined_path = if self.pattern.starts_with("/") {
PathBuf::from(&self.pattern)
} else {
PathBuf::from(&self.base).join(&self.pattern)
};
match dunce::canonicalize(combined_path) {
Ok(resolved_path) if resolved_path.is_dir() => {
self.base = resolved_path.to_string_lossy().to_string();
self.pattern = "**/*".to_owned();
}
Ok(resolved_path) if resolved_path.is_file() => {
self.base = resolved_path
.parent()
.unwrap()
.to_string_lossy()
.to_string();
self.pattern =
format!("/{}", resolved_path.file_name().unwrap().to_string_lossy());
}
_ => {}
}
return;
}
let (static_part, dynamic_part) = split_pattern(&self.pattern);
let base: PathBuf = self.base.clone().into();
let base = match static_part {
Some(static_part) => {
match dunce::canonicalize(base.join(static_part)) {
Ok(base) => base,
Err(err) => {
event!(tracing::Level::ERROR, "Failed to resolve glob: {:?}", err);
return;
}
}
}
None => base,
};
let pattern = match dynamic_part {
Some(dynamic_part) => dynamic_part,
None => {
if base.is_dir() {
"**/*".to_owned()
} else {
"".to_owned()
}
}
};
self.base = base.to_string_lossy().to_string();
self.pattern = pattern;
}
}
pub fn public_source_entries_to_private_source_entries(
sources: Vec<PublicSourceEntry>,
) -> Vec<SourceEntry> {
let expanded_globs = sources
.into_iter()
.flat_map(|source| {
let expression: Result<Expression, _> = source.pattern[..].try_into();
let Ok(expression) = expression else {
return vec![source];
};
expression
.into_iter()
.filter_map(Result::ok)
.map(move |pattern| PublicSourceEntry {
base: source.base.clone(),
pattern: pattern.into(),
negated: source.negated,
})
.collect::<Vec<_>>()
})
.map(|mut public_source| {
public_source.optimize();
public_source
})
.collect::<Vec<_>>();
expanded_globs
.into_iter()
.map(Into::into)
.collect::<Vec<_>>()
}
impl From<PublicSourceEntry> for SourceEntry {
fn from(value: PublicSourceEntry) -> Self {
let auto = value.pattern.ends_with("**/*")
|| PathBuf::from(&value.base).join(&value.pattern).is_dir();
let inside_ignored_content_dir = IGNORED_CONTENT_DIRS.iter().any(|dir| {
value.base.contains(&format!(
"{}{}{}",
std::path::MAIN_SEPARATOR,
dir,
std::path::MAIN_SEPARATOR
)) || value
.base
.ends_with(&format!("{}{}", std::path::MAIN_SEPARATOR, dir,))
});
match (value.negated, auto, inside_ignored_content_dir) {
(false, true, false) => SourceEntry::Auto {
base: value.base.into(),
},
(false, true, true) => SourceEntry::External {
base: value.base.into(),
},
(false, false, _) => SourceEntry::Pattern {
base: value.base.into(),
pattern: value.pattern,
},
(true, _, _) => SourceEntry::Ignored {
base: value.base.into(),
pattern: value.pattern,
},
}
}
}
impl From<GlobEntry> for SourceEntry {
fn from(value: GlobEntry) -> Self {
SourceEntry::Pattern {
base: PathBuf::from(value.base),
pattern: value.pattern,
}
}
}
impl From<SourceEntry> for GlobEntry {
fn from(value: SourceEntry) -> Self {
match value {
SourceEntry::Auto { base } | SourceEntry::External { base } => GlobEntry {
base: base.to_string_lossy().into(),
pattern: "**/*".into(),
},
SourceEntry::Pattern { base, pattern } => GlobEntry {
base: base.to_string_lossy().into(),
pattern: pattern.clone(),
},
SourceEntry::Ignored { base, pattern } => GlobEntry {
base: base.to_string_lossy().into(),
pattern: pattern.clone(),
},
}
}
}
impl From<&SourceEntry> for GlobEntry {
fn from(value: &SourceEntry) -> Self {
match value {
SourceEntry::Auto { base } | SourceEntry::External { base } => GlobEntry {
base: base.to_string_lossy().into(),
pattern: "**/*".into(),
},
SourceEntry::Pattern { base, pattern } => GlobEntry {
base: base.to_string_lossy().into(),
pattern: pattern.clone(),
},
SourceEntry::Ignored { base, pattern } => GlobEntry {
base: base.to_string_lossy().into(),
pattern: pattern.clone(),
},
}
}
}