use std::collections::{BTreeMap, HashMap};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use rumdl_lib::config as rumdl_config;
use rumdl_lib::rule::Rule;
use crate::cache::LintCache;
use crate::file_processor::CacheHashes;
pub struct ConfigGroup {
pub config: rumdl_config::Config,
pub rules: Vec<Box<dyn Rule>>,
pub cache_hashes: Option<Arc<CacheHashes>>,
pub files: Vec<String>,
}
fn is_root_level_config(config_path: &Path, project_root: &Path) -> bool {
if let Some(parent) = config_path.parent() {
if parent == project_root {
return true;
}
if parent == project_root.join(".config") {
return true;
}
}
false
}
pub fn resolve_config_groups(
file_paths: &[String],
root_config: &rumdl_config::Config,
args: &crate::CheckArgs,
project_root: Option<&Path>,
cache: &Option<Arc<Mutex<LintCache>>>,
explicit_config: bool,
isolated: bool,
) -> Vec<ConfigGroup> {
if explicit_config || isolated || project_root.is_none() {
let enabled_rules = crate::file_processor::get_enabled_rules_from_checkargs(args, root_config);
let cache_hashes = cache
.as_ref()
.map(|_| Arc::new(CacheHashes::new(root_config, &enabled_rules)));
return vec![ConfigGroup {
config: root_config.clone(),
rules: enabled_rules,
cache_hashes,
files: file_paths.to_vec(),
}];
}
let project_root = project_root.unwrap();
let mut dir_config_cache: HashMap<PathBuf, Option<PathBuf>> = HashMap::new();
let mut file_config_map: BTreeMap<Option<PathBuf>, Vec<String>> = BTreeMap::new();
for file_path in file_paths {
let path = Path::new(file_path);
let parent_dir = match path.parent() {
Some(dir) if dir.is_dir() => dir.to_path_buf(),
_ => project_root.to_path_buf(),
};
let config_path = discover_with_cache(&parent_dir, project_root, &mut dir_config_cache);
let effective_config = config_path.filter(|cp| !is_root_level_config(cp, project_root));
file_config_map
.entry(effective_config)
.or_default()
.push(file_path.clone());
}
let mut groups = Vec::new();
for (config_path, files) in file_config_map {
match config_path {
None => {
let enabled_rules = crate::file_processor::get_enabled_rules_from_checkargs(args, root_config);
let cache_hashes = cache
.as_ref()
.map(|_| Arc::new(CacheHashes::new(root_config, &enabled_rules)));
groups.push(ConfigGroup {
config: root_config.clone(),
rules: enabled_rules,
cache_hashes,
files,
});
}
Some(path) => {
match rumdl_config::SourcedConfig::load_config_for_path(&path, project_root) {
Ok(mut subdir_config) => {
apply_cli_config_overrides(&mut subdir_config, args);
let enabled_rules =
crate::file_processor::get_enabled_rules_from_checkargs(args, &subdir_config);
let cache_hashes = cache
.as_ref()
.map(|_| Arc::new(CacheHashes::new(&subdir_config, &enabled_rules)));
groups.push(ConfigGroup {
config: subdir_config,
rules: enabled_rules,
cache_hashes,
files,
});
}
Err(e) => {
eprintln!(
"\x1b[33m[config warning]\x1b[0m Failed to load config {}: {}. Using root config for affected files.",
path.display(),
e
);
let enabled_rules = crate::file_processor::get_enabled_rules_from_checkargs(args, root_config);
let cache_hashes = cache
.as_ref()
.map(|_| Arc::new(CacheHashes::new(root_config, &enabled_rules)));
groups.push(ConfigGroup {
config: root_config.clone(),
rules: enabled_rules,
cache_hashes,
files,
});
}
}
}
}
}
groups
}
fn discover_with_cache(
dir: &Path,
project_root: &Path,
cache: &mut HashMap<PathBuf, Option<PathBuf>>,
) -> Option<PathBuf> {
if let Some(cached) = cache.get(dir) {
return cached.clone();
}
let result = rumdl_config::SourcedConfig::discover_config_for_dir(dir, project_root);
cache.insert(dir.to_path_buf(), result.clone());
if let Some(ref config_path) = result {
if let Some(config_dir) = config_path.parent() {
let mut intermediate = dir.to_path_buf();
while intermediate != config_dir && intermediate.starts_with(project_root) {
cache.entry(intermediate.clone()).or_insert_with(|| result.clone());
match intermediate.parent() {
Some(parent) => intermediate = parent.to_path_buf(),
None => break,
}
}
}
} else {
let mut intermediate = dir.to_path_buf();
while intermediate.starts_with(project_root) {
cache.entry(intermediate.clone()).or_insert(None);
if intermediate == project_root.to_path_buf() {
break;
}
match intermediate.parent() {
Some(parent) => intermediate = parent.to_path_buf(),
None => break,
}
}
}
result
}
fn apply_cli_config_overrides(config: &mut rumdl_config::Config, args: &crate::CheckArgs) {
if let Some(flavor) = args.flavor {
config.global.flavor = flavor.into();
}
if let Some(respect_gitignore) = args.respect_gitignore {
config.global.respect_gitignore = respect_gitignore;
}
}