use orbok_core::{HiddenFilePolicy, SymlinkPolicy};
use orbok_db::repo::SourceRecord;
use std::path::Path;
pub const DEFAULT_EXCLUDES: &[&str] = &[
".git",
"node_modules",
"target",
"dist",
"build",
".cache",
".venv",
"__pycache__",
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileTypeClass {
Supported,
Unsupported,
}
const SUPPORTED_EXTENSIONS: &[&str] = &[
"txt", "log", "md", "markdown", "html", "htm", "pdf", "docx", "csv",
"rs", "py", "js", "ts", "jsx", "tsx", "java", "c", "h", "cpp", "hpp", "go", "rb", "php",
"sh", "bash", "sql", "toml", "yaml", "yml", "json", "xml", "css",
];
#[derive(Debug, Clone)]
pub struct CompiledPolicy {
pub hidden_file_policy: HiddenFilePolicy,
pub symlink_policy: SymlinkPolicy,
pub max_file_size_bytes: Option<u64>,
include_extensions: Vec<String>,
include_names: Vec<String>,
exclude_components: Vec<String>,
exclude_extensions: Vec<String>,
}
impl CompiledPolicy {
pub fn from_source(source: &SourceRecord) -> Self {
let mut include_extensions = Vec::new();
let mut include_names = Vec::new();
for pattern in &source.include_patterns {
match pattern.strip_prefix("*.") {
Some(ext) => include_extensions.push(ext.to_ascii_lowercase()),
None => include_names.push(pattern.clone()),
}
}
let mut exclude_components: Vec<String> =
DEFAULT_EXCLUDES.iter().map(|s| s.to_string()).collect();
let mut exclude_extensions = Vec::new();
for pattern in &source.exclude_patterns {
match pattern.strip_prefix("*.") {
Some(ext) => exclude_extensions.push(ext.to_ascii_lowercase()),
None => exclude_components.push(pattern.clone()),
}
}
Self {
hidden_file_policy: source.hidden_file_policy,
symlink_policy: source.symlink_policy,
max_file_size_bytes: source.max_file_size_bytes,
include_extensions,
include_names,
exclude_components,
exclude_extensions,
}
}
pub fn component_excluded(&self, name: &str) -> bool {
self.exclude_components.iter().any(|p| p == name)
}
pub fn component_hidden(name: &str) -> bool {
name.starts_with('.')
}
pub fn file_included(&self, file_name: &str) -> bool {
let ext = extension_of(file_name);
if let Some(ext) = &ext {
if self.exclude_extensions.iter().any(|e| e == ext) {
return false;
}
}
if self.component_excluded(file_name) {
return false;
}
if self.include_extensions.is_empty() && self.include_names.is_empty() {
return true;
}
if self.include_names.iter().any(|n| n == file_name) {
return true;
}
match ext {
Some(ext) => self.include_extensions.iter().any(|e| e == &ext),
None => false,
}
}
pub fn size_allowed(&self, size: u64) -> bool {
match self.max_file_size_bytes {
Some(max) => size <= max,
None => true,
}
}
}
pub fn classify_file_type(path: &Path) -> FileTypeClass {
match path
.extension()
.and_then(|e| e.to_str())
.map(|e| e.to_ascii_lowercase())
{
Some(ext) if SUPPORTED_EXTENSIONS.contains(&ext.as_str()) => FileTypeClass::Supported,
_ => FileTypeClass::Unsupported,
}
}
fn extension_of(file_name: &str) -> Option<String> {
Path::new(file_name)
.extension()
.and_then(|e| e.to_str())
.map(|e| e.to_ascii_lowercase())
}