use std::path::{Path, PathBuf};
use crate::context::ContextScope;
pub const DEFAULT_WORKSPACE_SKILLS_DIR: &str = ".basis/skills";
pub const DEFAULT_GLOBAL_SKILLS_DIR: &str = "skills";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SkillsConfig {
pub workspace_subdir: PathBuf,
pub global_dir: Option<PathBuf>,
}
impl Default for SkillsConfig {
fn default() -> Self {
Self {
workspace_subdir: PathBuf::from(DEFAULT_WORKSPACE_SKILLS_DIR),
global_dir: crate::context::ContextConfig::default().global_dir,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SkillsSource {
pub path: PathBuf,
pub scope: ContextScope,
}
pub fn discover(workspace: &Path, config: &SkillsConfig) -> Vec<SkillsSource> {
let mut sources = Vec::new();
let workspace_dir = workspace.join(&config.workspace_subdir);
if workspace_dir.is_dir() {
sources.push(SkillsSource {
path: workspace_dir,
scope: ContextScope::Workspace,
});
}
if let Some(global) = &config.global_dir {
let global_dir = global.join(DEFAULT_GLOBAL_SKILLS_DIR);
if global_dir.is_dir()
&& !sources
.iter()
.any(|source| crate::paths::same_dir(&source.path, &global_dir))
{
sources.push(SkillsSource {
path: global_dir,
scope: ContextScope::Global,
});
}
}
sources
}
#[cfg(test)]
mod tests {
use super::*;
fn config(global: Option<PathBuf>) -> SkillsConfig {
SkillsConfig {
workspace_subdir: PathBuf::from(DEFAULT_WORKSPACE_SKILLS_DIR),
global_dir: global,
}
}
#[test]
fn nothing_on_disk_means_no_sources() {
let tmp = tempfile::tempdir().expect("tempdir");
assert!(discover(tmp.path(), &config(None)).is_empty());
}
#[test]
fn a_workspace_directory_is_found() {
let tmp = tempfile::tempdir().expect("tempdir");
let skills = tmp.path().join(DEFAULT_WORKSPACE_SKILLS_DIR);
std::fs::create_dir_all(&skills).expect("create skills dir");
let found = discover(tmp.path(), &config(None));
assert_eq!(found.len(), 1);
assert_eq!(found[0].scope, ContextScope::Workspace);
assert_eq!(found[0].path, skills);
}
#[test]
fn the_workspace_directory_outranks_the_global_one() {
let tmp = tempfile::tempdir().expect("tempdir");
let workspace_skills = tmp.path().join(DEFAULT_WORKSPACE_SKILLS_DIR);
let global = tmp.path().join("global");
std::fs::create_dir_all(&workspace_skills).expect("create workspace skills");
std::fs::create_dir_all(global.join(DEFAULT_GLOBAL_SKILLS_DIR)).expect("create global");
let found = discover(tmp.path(), &config(Some(global)));
assert_eq!(found.len(), 2);
assert_eq!(found[0].scope, ContextScope::Workspace);
assert_eq!(found[1].scope, ContextScope::Global);
}
#[test]
fn a_global_directory_alone_is_used() {
let tmp = tempfile::tempdir().expect("tempdir");
let global = tmp.path().join("global");
std::fs::create_dir_all(global.join(DEFAULT_GLOBAL_SKILLS_DIR)).expect("create global");
let found = discover(tmp.path(), &config(Some(global)));
assert_eq!(found.len(), 1);
assert_eq!(found[0].scope, ContextScope::Global);
}
#[test]
fn a_file_where_the_directory_should_be_is_ignored() {
let tmp = tempfile::tempdir().expect("tempdir");
let skills = tmp.path().join(DEFAULT_WORKSPACE_SKILLS_DIR);
std::fs::create_dir_all(skills.parent().expect("parent")).expect("create .basis");
std::fs::write(&skills, "not a directory").expect("write file");
assert!(discover(tmp.path(), &config(None)).is_empty());
}
#[test]
fn the_same_directory_reached_twice_is_reported_once() {
let tmp = tempfile::tempdir().expect("tempdir");
let global = tmp.path().join("global");
let global_skills = global.join(DEFAULT_GLOBAL_SKILLS_DIR);
std::fs::create_dir_all(&global_skills).expect("create global skills");
let found = discover(
&global,
&SkillsConfig {
workspace_subdir: PathBuf::from(DEFAULT_GLOBAL_SKILLS_DIR),
global_dir: Some(global.clone()),
},
);
assert_eq!(found.len(), 1);
assert_eq!(found[0].scope, ContextScope::Workspace);
}
}