feat: Migrate SAL to Cargo workspace
Some checks failed
Rhai Tests / Run Rhai Tests (push) Has been cancelled
Rhai Tests / Run Rhai Tests (pull_request) Has been cancelled

- Migrate individual modules to independent crates
- Refactor dependencies for improved modularity
- Update build system and testing infrastructure
- Update documentation to reflect new structure
This commit is contained in:
Mahmoud-Emad
2025-06-24 12:39:18 +03:00
parent 8012a66250
commit e125bb6511
54 changed files with 1196 additions and 1582 deletions

View File

@@ -1,9 +1,9 @@
use std::process::Command;
use std::path::Path;
use std::fs;
use regex::Regex;
use std::fmt;
use std::error::Error;
use std::fmt;
use std::fs;
use std::path::Path;
use std::process::Command;
// Define a custom error type for git operations
#[derive(Debug)]
@@ -35,7 +35,7 @@ impl fmt::Display for GitError {
GitError::CommandExecutionError(e) => write!(f, "Error executing command: {}", e),
GitError::NoRepositoriesFound => write!(f, "No repositories found"),
GitError::RepositoryNotFound(pattern) => write!(f, "No repositories found matching '{}'", pattern),
GitError::MultipleRepositoriesFound(pattern, count) =>
GitError::MultipleRepositoriesFound(pattern, count) =>
write!(f, "Multiple repositories ({}) found matching '{}'. Use '*' suffix for multiple matches.", count, pattern),
GitError::NotAGitRepository(path) => write!(f, "Not a git repository at {}", path),
GitError::LocalChangesExist(path) => write!(f, "Repository at {} has local changes", path),
@@ -57,48 +57,48 @@ impl Error for GitError {
}
/// Parses a git URL to extract the server, account, and repository name.
///
///
/// # Arguments
///
/// * `url` - The URL of the git repository to parse. Can be in HTTPS format
///
/// * `url` - The URL of the git repository to parse. Can be in HTTPS format
/// (https://github.com/username/repo.git) or SSH format (git@github.com:username/repo.git).
///
///
/// # Returns
///
///
/// A tuple containing:
/// * `server` - The server name (e.g., "github.com")
/// * `account` - The account or organization name (e.g., "username")
/// * `repo` - The repository name (e.g., "repo")
///
///
/// If the URL cannot be parsed, all three values will be empty strings.
pub fn parse_git_url(url: &str) -> (String, String, String) {
// HTTP(S) URL format: https://github.com/username/repo.git
let https_re = Regex::new(r"https?://([^/]+)/([^/]+)/([^/\.]+)(?:\.git)?").unwrap();
// SSH URL format: git@github.com:username/repo.git
let ssh_re = Regex::new(r"git@([^:]+):([^/]+)/([^/\.]+)(?:\.git)?").unwrap();
if let Some(caps) = https_re.captures(url) {
let server = caps.get(1).map_or("", |m| m.as_str()).to_string();
let account = caps.get(2).map_or("", |m| m.as_str()).to_string();
let repo = caps.get(3).map_or("", |m| m.as_str()).to_string();
return (server, account, repo);
} else if let Some(caps) = ssh_re.captures(url) {
let server = caps.get(1).map_or("", |m| m.as_str()).to_string();
let account = caps.get(2).map_or("", |m| m.as_str()).to_string();
let repo = caps.get(3).map_or("", |m| m.as_str()).to_string();
return (server, account, repo);
}
(String::new(), String::new(), String::new())
}
/// Checks if git is installed on the system.
///
///
/// # Returns
///
///
/// * `Ok(())` - If git is installed
/// * `Err(GitError)` - If git is not installed
fn check_git_installed() -> Result<(), GitError> {
@@ -117,55 +117,53 @@ pub struct GitTree {
impl GitTree {
/// Creates a new GitTree with the specified base path.
///
///
/// # Arguments
///
///
/// * `base_path` - The base path where all git repositories are located
///
///
/// # Returns
///
///
/// * `Ok(GitTree)` - A new GitTree instance
/// * `Err(GitError)` - If the base path is invalid or cannot be created
pub fn new(base_path: &str) -> Result<Self, GitError> {
// Check if git is installed
check_git_installed()?;
// Validate the base path
let path = Path::new(base_path);
if !path.exists() {
fs::create_dir_all(path).map_err(|e| {
GitError::FileSystemError(e)
})?;
fs::create_dir_all(path).map_err(|e| GitError::FileSystemError(e))?;
} else if !path.is_dir() {
return Err(GitError::InvalidBasePath(base_path.to_string()));
}
Ok(GitTree {
base_path: base_path.to_string(),
})
}
/// Lists all git repositories under the base path.
///
///
/// # Returns
///
///
/// * `Ok(Vec<String>)` - A vector of paths to git repositories
/// * `Err(GitError)` - If the operation failed
pub fn list(&self) -> Result<Vec<String>, GitError> {
let base_path = Path::new(&self.base_path);
if !base_path.exists() || !base_path.is_dir() {
return Ok(Vec::new());
}
let mut repos = Vec::new();
// Find all directories with .git subdirectories
let output = Command::new("find")
.args(&[&self.base_path, "-type", "d", "-name", ".git"])
.output()
.map_err(GitError::CommandExecutionError)?;
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
@@ -178,22 +176,25 @@ impl GitTree {
}
} else {
let error = String::from_utf8_lossy(&output.stderr);
return Err(GitError::GitCommandFailed(format!("Failed to find git repositories: {}", error)));
return Err(GitError::GitCommandFailed(format!(
"Failed to find git repositories: {}",
error
)));
}
Ok(repos)
}
/// Finds repositories matching a pattern or partial path.
///
///
/// # Arguments
///
///
/// * `pattern` - The pattern to match against repository paths
/// - If the pattern ends with '*', all matching repositories are returned
/// - Otherwise, exactly one matching repository must be found
///
///
/// # Returns
///
///
/// * `Ok(Vec<String>)` - A vector of paths to matching repositories
/// * `Err(GitError)` - If no matching repositories are found,
/// or if multiple repositories match a non-wildcard pattern
@@ -212,7 +213,7 @@ impl GitTree {
matched_repos.push(GitRepo::new(full_path));
}
} else if pattern.ends_with('*') {
let prefix = &pattern[0..pattern.len()-1];
let prefix = &pattern[0..pattern.len() - 1];
for name in repo_names {
if name.starts_with(prefix) {
let full_path = format!("{}/{}", self.base_path, name);
@@ -233,17 +234,17 @@ impl GitTree {
Ok(matched_repos)
}
/// Gets one or more GitRepo objects based on a path pattern or URL.
///
///
/// # Arguments
///
///
/// * `path_or_url` - The path pattern to match against repository paths or a git URL
/// - If it's a URL, the repository will be cloned if it doesn't exist
/// - If it's a path pattern, it will find matching repositories
///
///
/// # Returns
///
///
/// * `Ok(Vec<GitRepo>)` - A vector of GitRepo objects
/// * `Err(GitError)` - If no matching repositories are found or the clone operation failed
pub fn get(&self, path_or_url: &str) -> Result<Vec<GitRepo>, GitError> {
@@ -254,32 +255,35 @@ impl GitTree {
if server.is_empty() || account.is_empty() || repo.is_empty() {
return Err(GitError::InvalidUrl(path_or_url.to_string()));
}
// Create the target directory
let clone_path = format!("{}/{}/{}/{}", self.base_path, server, account, repo);
let clone_dir = Path::new(&clone_path);
// Check if repo already exists
if clone_dir.exists() {
return Ok(vec![GitRepo::new(clone_path)]);
}
// Create parent directory
if let Some(parent) = clone_dir.parent() {
fs::create_dir_all(parent).map_err(GitError::FileSystemError)?;
}
// Clone the repository
let output = Command::new("git")
.args(&["clone", "--depth", "1", path_or_url, &clone_path])
.output()
.map_err(GitError::CommandExecutionError)?;
if output.status.success() {
Ok(vec![GitRepo::new(clone_path)])
} else {
let error = String::from_utf8_lossy(&output.stderr);
Err(GitError::GitCommandFailed(format!("Git clone error: {}", error)))
Err(GitError::GitCommandFailed(format!(
"Git clone error: {}",
error
)))
}
} else {
// It's a path pattern, find matching repositories using the updated self.find()
@@ -357,7 +361,10 @@ impl GitRepo {
Ok(self.clone())
} else {
let error = String::from_utf8_lossy(&output.stderr);
Err(GitError::GitCommandFailed(format!("Git pull error: {}", error)))
Err(GitError::GitCommandFailed(format!(
"Git pull error: {}",
error
)))
}
}
@@ -382,7 +389,10 @@ impl GitRepo {
if !reset_output.status.success() {
let error = String::from_utf8_lossy(&reset_output.stderr);
return Err(GitError::GitCommandFailed(format!("Git reset error: {}", error)));
return Err(GitError::GitCommandFailed(format!(
"Git reset error: {}",
error
)));
}
// Clean untracked files
@@ -393,7 +403,10 @@ impl GitRepo {
if !clean_output.status.success() {
let error = String::from_utf8_lossy(&clean_output.stderr);
return Err(GitError::GitCommandFailed(format!("Git clean error: {}", error)));
return Err(GitError::GitCommandFailed(format!(
"Git clean error: {}",
error
)));
}
Ok(self.clone())
@@ -429,7 +442,10 @@ impl GitRepo {
if !add_output.status.success() {
let error = String::from_utf8_lossy(&add_output.stderr);
return Err(GitError::GitCommandFailed(format!("Git add error: {}", error)));
return Err(GitError::GitCommandFailed(format!(
"Git add error: {}",
error
)));
}
// Commit the changes
@@ -440,7 +456,10 @@ impl GitRepo {
if !commit_output.status.success() {
let error = String::from_utf8_lossy(&commit_output.stderr);
return Err(GitError::GitCommandFailed(format!("Git commit error: {}", error)));
return Err(GitError::GitCommandFailed(format!(
"Git commit error: {}",
error
)));
}
Ok(self.clone())
@@ -469,7 +488,10 @@ impl GitRepo {
Ok(self.clone())
} else {
let error = String::from_utf8_lossy(&push_output.stderr);
Err(GitError::GitCommandFailed(format!("Git push error: {}", error)))
Err(GitError::GitCommandFailed(format!(
"Git push error: {}",
error
)))
}
}
}