use std::path::PathBuf;
use std::env;
pub trait WorkspaceContext {
fn get_directories(&self) -> Vec<PathBuf>;
}
pub struct BasicWorkspaceContext;
impl BasicWorkspaceContext {
pub fn new() -> Self {
Self
}
}
impl Default for BasicWorkspaceContext {
fn default() -> Self {
Self::new()
}
}
impl WorkspaceContext for BasicWorkspaceContext {
fn get_directories(&self) -> Vec<PathBuf> {
vec![env::current_dir().unwrap_or_else(|_| PathBuf::from("."))]
}
}
pub struct CustomWorkspaceContext {
directories: Vec<PathBuf>,
}
impl CustomWorkspaceContext {
pub fn new(directories: Vec<PathBuf>) -> Self {
Self { directories }
}
pub fn from_paths(paths: Vec<String>) -> Self {
let directories = paths.into_iter().map(PathBuf::from).collect();
Self::new(directories)
}
pub fn add_directory(&mut self, directory: PathBuf) {
self.directories.push(directory);
}
pub fn remove_directory(&mut self, directory: &PathBuf) {
self.directories.retain(|d| d != directory);
}
pub fn len(&self) -> usize {
self.directories.len()
}
pub fn is_empty(&self) -> bool {
self.directories.is_empty()
}
}
impl WorkspaceContext for CustomWorkspaceContext {
fn get_directories(&self) -> Vec<PathBuf> {
self.directories.clone()
}
}
pub struct SmartWorkspaceContext {
directories: Vec<PathBuf>,
}
impl SmartWorkspaceContext {
pub fn new() -> Self {
let mut directories = Vec::new();
let mut current_dir = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
loop {
if Self::is_project_root(¤t_dir) {
directories.push(current_dir.clone());
}
if let Some(parent) = current_dir.parent() {
current_dir = parent.to_path_buf();
} else {
break;
}
if directories.len() >= 5 {
break;
}
}
if directories.is_empty() {
directories.push(env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
}
Self { directories }
}
fn is_project_root(path: &PathBuf) -> bool {
let project_indicators = [
"Cargo.toml", "package.json", "pyproject.toml", ".git", "Makefile", "CMakeLists.txt", "go.mod", "pom.xml", "build.gradle", ];
project_indicators.iter().any(|indicator| {
path.join(indicator).exists()
})
}
pub fn add_directory(&mut self, directory: PathBuf) {
if !self.directories.contains(&directory) {
self.directories.push(directory);
}
}
pub fn remove_directory(&mut self, directory: &PathBuf) {
self.directories.retain(|d| d != directory);
}
pub fn refresh(&mut self) {
*self = Self::new();
}
}
impl Default for SmartWorkspaceContext {
fn default() -> Self {
Self::new()
}
}
impl WorkspaceContext for SmartWorkspaceContext {
fn get_directories(&self) -> Vec<PathBuf> {
self.directories.clone()
}
}
pub struct WorkspaceContextBuilder {
directories: Vec<PathBuf>,
use_smart_detection: bool,
}
impl WorkspaceContextBuilder {
pub fn new() -> Self {
Self {
directories: Vec::new(),
use_smart_detection: false,
}
}
pub fn add_directory(mut self, directory: PathBuf) -> Self {
self.directories.push(directory);
self
}
pub fn add_directories(mut self, directories: Vec<PathBuf>) -> Self {
self.directories.extend(directories);
self
}
pub fn with_smart_detection(mut self) -> Self {
self.use_smart_detection = true;
self
}
pub fn build(self) -> Box<dyn WorkspaceContext + Send + Sync> {
if self.use_smart_detection {
let mut smart_context = SmartWorkspaceContext::new();
for directory in self.directories {
smart_context.add_directory(directory);
}
Box::new(smart_context)
} else if self.directories.is_empty() {
Box::new(BasicWorkspaceContext::new())
} else {
Box::new(CustomWorkspaceContext::new(self.directories))
}
}
}
impl Default for WorkspaceContextBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct WorkspaceContextFactory;
impl WorkspaceContextFactory {
pub fn create_basic() -> Box<dyn WorkspaceContext + Send + Sync> {
Box::new(BasicWorkspaceContext::new())
}
pub fn create_custom(directories: Vec<PathBuf>) -> Box<dyn WorkspaceContext + Send + Sync> {
Box::new(CustomWorkspaceContext::new(directories))
}
pub fn create_smart() -> Box<dyn WorkspaceContext + Send + Sync> {
Box::new(SmartWorkspaceContext::new())
}
pub fn from_current_dir() -> Box<dyn WorkspaceContext + Send + Sync> {
let current_dir = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
Box::new(CustomWorkspaceContext::new(vec![current_dir]))
}
pub fn from_env() -> Box<dyn WorkspaceContext + Send + Sync> {
if let Ok(workspace_dirs) = env::var("ALOU_WORKSPACE_DIRS") {
let directories: Vec<PathBuf> = workspace_dirs
.split(',')
.map(|s| PathBuf::from(s.trim()))
.filter(|p| p.exists())
.collect();
if !directories.is_empty() {
return Box::new(CustomWorkspaceContext::new(directories));
}
}
Self::create_smart()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_basic_workspace_context() {
let context = BasicWorkspaceContext::new();
let directories = context.get_directories();
assert!(!directories.is_empty());
}
#[test]
fn test_custom_workspace_context() {
let temp_dir = TempDir::new().unwrap();
let path = temp_dir.path().to_path_buf();
let context = CustomWorkspaceContext::new(vec![path.clone()]);
let directories = context.get_directories();
assert_eq!(directories.len(), 1);
assert_eq!(directories[0], path);
}
#[test]
fn test_smart_workspace_context() {
let context = SmartWorkspaceContext::new();
let directories = context.get_directories();
assert!(!directories.is_empty());
}
#[test]
fn test_workspace_context_builder() {
let temp_dir = TempDir::new().unwrap();
let path = temp_dir.path().to_path_buf();
let context = WorkspaceContextBuilder::new()
.add_directory(path.clone())
.build();
let directories = context.get_directories();
assert_eq!(directories.len(), 1);
assert_eq!(directories[0], path);
}
#[test]
fn test_workspace_context_factory() {
let basic_context = WorkspaceContextFactory::create_basic();
assert!(!basic_context.get_directories().is_empty());
let smart_context = WorkspaceContextFactory::create_smart();
assert!(!smart_context.get_directories().is_empty());
}
}