use std::collections::{BTreeMap, BTreeSet};
use std::path::{Path, PathBuf};
use crate::domain::config::{Agent, Config, RelevanceMode, Rule};
use crate::domain::ignore;
use crate::errors::{Error, Result};
use crate::io::configfs::RuleScope;
use crate::io::files;
pub fn known_rules(config: &Config) -> BTreeSet<&str> {
config.rules.iter().map(|r| r.name.as_str()).collect()
}
pub fn target_files(
cwd: &Path,
config: &Config,
scopes: &BTreeMap<String, RuleScope>,
cli_files: &[PathBuf],
) -> Result<BTreeSet<PathBuf>> {
let mut out: BTreeSet<PathBuf> = BTreeSet::new();
for rule in &config.rules {
if matches!(rule.relevance_mode(), RelevanceMode::Never) {
continue;
}
let agent_name = rule.agent.clone().unwrap_or_else(|| "default".to_string());
let agent = config.agent_or_default(&agent_name);
let fallback;
let scope = match scopes.get(&rule.name) {
Some(s) => s,
None => {
fallback = RuleScope {
dir: cwd.to_path_buf(),
files: config.files.clone(),
};
&fallback
}
};
for f in resolve_files(cwd, rule, &agent, cli_files, scope)? {
out.insert(f);
}
}
Ok(out)
}
pub fn resolve_files(
cwd: &Path,
rule: &Rule,
agent: &Agent,
cli_files: &[PathBuf],
scope: &RuleScope,
) -> Result<Vec<PathBuf>> {
if let Some(f) = &rule.files {
return files::resolve_scoped(&scope.dir, cwd, f);
}
if let Some(f) = &agent.files {
return files::resolve_scoped(&scope.dir, cwd, f);
}
if !cli_files.is_empty() {
return Ok(cli_files.to_vec());
}
files::resolve_scoped(&scope.dir, cwd, &scope.files)
}
pub fn check(cwd: &Path, targets: &BTreeSet<PathBuf>, known: &BTreeSet<&str>) -> Result<()> {
let mut problems: Vec<String> = Vec::new();
for rel in targets {
let Some(text) = files::read_text(cwd, rel)? else {
continue;
};
for p in ignore::validate(&text, known) {
problems.push(format!(
" {}:{}: {}",
files::to_slash(rel),
p.line,
p.message
));
}
}
if problems.is_empty() {
Ok(())
} else {
Err(Error::IgnoreDirective(problems.join("\n")))
}
}