use std::path::Path;
use std::sync::LazyLock;
use crate::{
init::find_root,
learn,
pattern::{self, Pattern},
};
pub static PATTERNS: LazyLock<Vec<Pattern>> = LazyLock::new(all_patterns);
pub fn all_patterns() -> Vec<Pattern> {
compose(load_project_patterns(), &learn::patterns_dir())
}
fn compose(mut all: Vec<Pattern>, user_dir: &Path) -> Vec<Pattern> {
all.extend(pattern::load_user_patterns(user_dir));
all.extend_from_slice(pattern::builtins());
all
}
pub fn load_project_patterns() -> Vec<Pattern> {
let Ok(cwd) = std::env::current_dir() else {
return Vec::new();
};
project_patterns_in(&cwd)
}
fn project_patterns_in(cwd: &Path) -> Vec<Pattern> {
pattern::load_user_patterns(&find_root(cwd).join(".oo").join("patterns"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_patterns_is_nonempty_and_includes_builtins() {
let all = all_patterns();
assert!(
!all.is_empty(),
"the canonical pattern set must not be empty"
);
let builtin_matches: Vec<&str> = pattern::builtins()
.iter()
.map(|p| p.command_match.as_str())
.collect();
assert!(
builtin_matches
.iter()
.any(|bm| { all.iter().any(|p| p.command_match.as_str() == *bm) }),
"all_patterns must include the builtin patterns"
);
}
#[test]
fn all_patterns_precedence_is_project_user_builtin() {
let project = tempfile::tempdir().unwrap();
std::fs::create_dir(project.path().join(".git")).unwrap();
let project_dir = project.path().join(".oo").join("patterns");
std::fs::create_dir_all(&project_dir).unwrap();
std::fs::write(
project_dir.join("project.toml"),
"command_match = \"^oo-precedence-project$\"\n",
)
.unwrap();
let config = tempfile::tempdir().unwrap();
let user_dir = config.path().join("patterns");
std::fs::create_dir_all(&user_dir).unwrap();
std::fs::write(
user_dir.join("user.toml"),
"command_match = \"^oo-precedence-user$\"\n",
)
.unwrap();
let all = compose(project_patterns_in(project.path()), &user_dir);
let position = |marker: &str| all.iter().position(|p| p.command_match.as_str() == marker);
let project_at = position("^oo-precedence-project$").expect("project tier missing");
let user_at = position("^oo-precedence-user$").expect("user tier missing");
let builtins = pattern::builtins();
let builtin_start = all
.len()
.checked_sub(builtins.len())
.expect("all_patterns must contain every builtin");
assert!(
project_at < user_at && user_at < builtin_start,
"precedence must be project({project_at}) < user({user_at}) < builtin({builtin_start})"
);
for (got, want) in all[builtin_start..].iter().zip(builtins) {
assert_eq!(
got.command_match.as_str(),
want.command_match.as_str(),
"builtin tier must be last, in declared order"
);
}
}
}