This commit is contained in:
2025-04-04 21:51:31 +02:00
parent 5b006ff328
commit 9f33c94020
19 changed files with 1221 additions and 88 deletions

115
src/rhai/git.rs Normal file
View File

@@ -0,0 +1,115 @@
//! Rhai wrappers for Git module functions
//!
//! This module provides Rhai wrappers for the functions in the Git module.
use rhai::{Engine, EvalAltResult, Array, Dynamic};
use crate::git::{self, GitError};
/// Register Git module functions with the Rhai engine
///
/// # Arguments
///
/// * `engine` - The Rhai engine to register the functions with
///
/// # Returns
///
/// * `Result<(), Box<EvalAltResult>>` - Ok if registration was successful, Err otherwise
pub fn register_git_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
// Register basic git functions
engine.register_fn("git_clone", git_clone);
engine.register_fn("git_list", git_list);
engine.register_fn("git_update", git_update);
engine.register_fn("git_update_force", git_update_force);
engine.register_fn("git_update_commit", git_update_commit);
engine.register_fn("git_update_commit_push", git_update_commit_push);
engine.register_fn("git_has_changes", has_git_changes);
engine.register_fn("git_find_repos", find_matching_repos);
Ok(())
}
// Helper functions for error conversion
fn git_error_to_rhai_error<T>(result: Result<T, GitError>) -> Result<T, Box<EvalAltResult>> {
result.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Git error: {}", e).into(),
rhai::Position::NONE
))
})
}
//
// Git Function Wrappers
//
/// Wrapper for git::git_clone
///
/// Clones a git repository to a standardized location in the user's home directory.
pub fn git_clone(url: &str) -> Result<String, Box<EvalAltResult>> {
git_error_to_rhai_error(git::git_clone(url))
}
/// Wrapper for git::git_list
///
/// Lists all git repositories found in the user's ~/code directory.
pub fn git_list() -> Result<Array, Box<EvalAltResult>> {
let repos = git_error_to_rhai_error(git::git_list())?;
// Convert Vec<String> to Rhai Array
let mut array = Array::new();
for repo in repos {
array.push(Dynamic::from(repo));
}
Ok(array)
}
/// Wrapper for git::has_git_changes
///
/// Checks if a git repository has uncommitted changes.
pub fn has_git_changes(repo_path: &str) -> Result<bool, Box<EvalAltResult>> {
git_error_to_rhai_error(git::has_git_changes(repo_path))
}
/// Wrapper for git::find_matching_repos
///
/// Finds repositories matching a pattern or partial path.
pub fn find_matching_repos(pattern: &str) -> Result<Array, Box<EvalAltResult>> {
let repos = git_error_to_rhai_error(git::find_matching_repos(pattern))?;
// Convert Vec<String> to Rhai Array
let mut array = Array::new();
for repo in repos {
array.push(Dynamic::from(repo));
}
Ok(array)
}
/// Wrapper for git::git_update
///
/// Updates a git repository by pulling the latest changes.
pub fn git_update(repo_path: &str) -> Result<String, Box<EvalAltResult>> {
git_error_to_rhai_error(git::git_update(repo_path))
}
/// Wrapper for git::git_update_force
///
/// Force updates a git repository by discarding local changes and pulling the latest changes.
pub fn git_update_force(repo_path: &str) -> Result<String, Box<EvalAltResult>> {
git_error_to_rhai_error(git::git_update_force(repo_path))
}
/// Wrapper for git::git_update_commit
///
/// Commits changes in a git repository and then updates it by pulling the latest changes.
pub fn git_update_commit(repo_path: &str, message: &str) -> Result<String, Box<EvalAltResult>> {
git_error_to_rhai_error(git::git_update_commit(repo_path, message))
}
/// Wrapper for git::git_update_commit_push
///
/// Commits changes in a git repository and pushes them to the remote.
pub fn git_update_commit_push(repo_path: &str, message: &str) -> Result<String, Box<EvalAltResult>> {
git_error_to_rhai_error(git::git_update_commit_push(repo_path, message))
}

View File

@@ -7,6 +7,8 @@ mod error;
mod os;
mod process;
mod buildah;
mod nerdctl;
mod git;
#[cfg(test)]
mod tests;
@@ -35,7 +37,28 @@ pub use process::{
which, kill, process_list, process_get
};
pub use buildah::*;
// Re-export buildah functions
pub use buildah::register_bah_module;
pub use buildah::{
bah_from, bah_run, bah_run_with_isolation, bah_copy, bah_add, bah_commit,
bah_remove, bah_list, bah_build_with_options,
new_commit_options, new_config_options, image_commit_with_options, config_with_options
};
// Re-export nerdctl functions
pub use nerdctl::register_nerdctl_module;
pub use nerdctl::{
nerdctl_run, nerdctl_exec,
nerdctl_copy, nerdctl_stop, nerdctl_remove, nerdctl_list
};
// Re-export git functions
pub use git::register_git_module;
pub use git::{
git_clone, git_list, git_update, git_update_force, git_update_commit,
git_update_commit_push, has_git_changes, find_matching_repos
};
// Rename copy functions to avoid conflicts
pub use os::copy as os_copy;
@@ -67,6 +90,12 @@ pub fn register(engine: &mut Engine) -> Result<(), Box<rhai::EvalAltResult>> {
// Register Buildah module functions
buildah::register_bah_module(engine)?;
// Register Nerdctl module functions
nerdctl::register_nerdctl_module(engine)?;
// Register Git module functions
git::register_git_module(engine)?;
// Future modules can be registered here
Ok(())

188
src/rhai/nerdctl.rs Normal file
View File

@@ -0,0 +1,188 @@
//! Rhai wrappers for Nerdctl module functions
//!
//! This module provides Rhai wrappers for the functions in the Nerdctl module.
use rhai::{Engine, EvalAltResult, Array, Dynamic, Map};
use crate::virt::nerdctl::{self, NerdctlError, Image};
use crate::process::CommandResult;
/// Register Nerdctl module functions with the Rhai engine
///
/// # Arguments
///
/// * `engine` - The Rhai engine to register the functions with
///
/// # Returns
///
/// * `Result<(), Box<EvalAltResult>>` - Ok if registration was successful, Err otherwise
pub fn register_nerdctl_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
// Register types
register_nerdctl_types(engine)?;
// Register container functions
engine.register_fn("nerdctl_run", nerdctl_run);
engine.register_fn("nerdctl_run_with_name", nerdctl_run_with_name);
engine.register_fn("nerdctl_run_with_port", nerdctl_run_with_port);
engine.register_fn("new_run_options", new_run_options);
engine.register_fn("nerdctl_exec", nerdctl_exec);
engine.register_fn("nerdctl_copy", nerdctl_copy);
engine.register_fn("nerdctl_stop", nerdctl_stop);
engine.register_fn("nerdctl_remove", nerdctl_remove);
engine.register_fn("nerdctl_list", nerdctl_list);
// Register image functions
engine.register_fn("nerdctl_images", nerdctl_images);
engine.register_fn("nerdctl_image_remove", nerdctl_image_remove);
engine.register_fn("nerdctl_image_push", nerdctl_image_push);
engine.register_fn("nerdctl_image_tag", nerdctl_image_tag);
engine.register_fn("nerdctl_image_pull", nerdctl_image_pull);
engine.register_fn("nerdctl_image_commit", nerdctl_image_commit);
engine.register_fn("nerdctl_image_build", nerdctl_image_build);
Ok(())
}
/// Register Nerdctl module types with the Rhai engine
fn register_nerdctl_types(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
// Register Image type and methods
engine.register_type_with_name::<Image>("NerdctlImage");
// Register getters for Image properties
engine.register_get("id", |img: &mut Image| img.id.clone());
engine.register_get("repository", |img: &mut Image| img.repository.clone());
engine.register_get("tag", |img: &mut Image| img.tag.clone());
engine.register_get("size", |img: &mut Image| img.size.clone());
engine.register_get("created", |img: &mut Image| img.created.clone());
Ok(())
}
// Helper functions for error conversion
fn nerdctl_error_to_rhai_error<T>(result: Result<T, NerdctlError>) -> Result<T, Box<EvalAltResult>> {
result.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Nerdctl error: {}", e).into(),
rhai::Position::NONE
))
})
}
/// Create a new Map with default run options
pub fn new_run_options() -> Map {
let mut map = Map::new();
map.insert("name".into(), Dynamic::UNIT);
map.insert("detach".into(), Dynamic::from(true));
map.insert("ports".into(), Dynamic::from(Array::new()));
map.insert("snapshotter".into(), Dynamic::from("native"));
map
}
//
// Container Function Wrappers
//
/// Wrapper for nerdctl::run
///
/// Run a container from an image.
pub fn nerdctl_run(image: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::run(image, None, true, None, None))
}
/// Run a container with a name
pub fn nerdctl_run_with_name(image: &str, name: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::run(image, Some(name), true, None, None))
}
/// Run a container with a port mapping
pub fn nerdctl_run_with_port(image: &str, name: &str, port: &str) -> Result<CommandResult, Box<EvalAltResult>> {
let ports = vec![port];
nerdctl_error_to_rhai_error(nerdctl::run(image, Some(name), true, Some(&ports), None))
}
/// Wrapper for nerdctl::exec
///
/// Execute a command in a container.
pub fn nerdctl_exec(container: &str, command: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::exec(container, command))
}
/// Wrapper for nerdctl::copy
///
/// Copy files between container and local filesystem.
pub fn nerdctl_copy(source: &str, dest: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::copy(source, dest))
}
/// Wrapper for nerdctl::stop
///
/// Stop a container.
pub fn nerdctl_stop(container: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::stop(container))
}
/// Wrapper for nerdctl::remove
///
/// Remove a container.
pub fn nerdctl_remove(container: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::remove(container))
}
/// Wrapper for nerdctl::list
///
/// List containers.
pub fn nerdctl_list(all: bool) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::list(all))
}
//
// Image Function Wrappers
//
/// Wrapper for nerdctl::images
///
/// List images in local storage.
pub fn nerdctl_images() -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::images())
}
/// Wrapper for nerdctl::image_remove
///
/// Remove one or more images.
pub fn nerdctl_image_remove(image: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::image_remove(image))
}
/// Wrapper for nerdctl::image_push
///
/// Push an image to a registry.
pub fn nerdctl_image_push(image: &str, destination: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::image_push(image, destination))
}
/// Wrapper for nerdctl::image_tag
///
/// Add an additional name to a local image.
pub fn nerdctl_image_tag(image: &str, new_name: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::image_tag(image, new_name))
}
/// Wrapper for nerdctl::image_pull
///
/// Pull an image from a registry.
pub fn nerdctl_image_pull(image: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::image_pull(image))
}
/// Wrapper for nerdctl::image_commit
///
/// Commit a container to an image.
pub fn nerdctl_image_commit(container: &str, image_name: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::image_commit(container, image_name))
}
/// Wrapper for nerdctl::image_build
///
/// Build an image using a Dockerfile.
pub fn nerdctl_image_build(tag: &str, context_path: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::image_build(tag, context_path))
}

View File

@@ -205,4 +205,50 @@ mod tests {
let result = engine.eval::<bool>(script).unwrap();
assert!(result);
}
// Git Module Tests
#[test]
fn test_git_module_registration() {
let mut engine = Engine::new();
register(&mut engine).unwrap();
// Test that git functions are registered
let script = r#"
// Check if git_clone function exists
let fn_exists = is_def_fn("git_clone");
fn_exists
"#;
let result = engine.eval::<bool>(script).unwrap();
assert!(result);
}
#[test]
fn test_git_parse_url() {
let mut engine = Engine::new();
register(&mut engine).unwrap();
// Test parsing a git URL
let script = r#"
// We can't directly test git_clone without actually cloning,
// but we can test that the function exists and doesn't error
// when called with invalid parameters
let result = false;
try {
// This should fail but not crash
git_clone("invalid-url");
} catch(err) {
// Expected error
result = err.contains("Git error");
}
result
"#;
let result = engine.eval::<bool>(script).unwrap();
assert!(result);
}
}

View File

@@ -0,0 +1,131 @@
// Test script for Git module functions
// Import required modules
import "os" as os;
import "process" as process;
// Test git_clone function
fn test_git_clone() {
// Use a public repository for testing
let repo_url = "https://github.com/rhaiscript/rhai.git";
// Clone the repository
print("Testing git_clone...");
let result = git_clone(repo_url);
// Print the result
print(`Clone result: ${result}`);
// Verify the repository exists
if result.contains("already exists") {
print("Repository already exists, test passed");
return true;
}
// Check if the path exists
if exist(result) {
print("Repository cloned successfully, test passed");
return true;
}
print("Repository clone failed");
return false;
}
// Test git_list function
fn test_git_list() {
print("Testing git_list...");
let repos = git_list();
print(`Found ${repos.len()} repositories`);
// Print the first few repositories
let count = if repos.len() > 3 { 3 } else { repos.len() };
for i in range(0, count) {
print(` - ${repos[i]}`);
}
return repos.len() > 0;
}
// Test git_has_changes function
fn test_git_has_changes() {
print("Testing git_has_changes...");
// Get a repository from the list
let repos = git_list();
if repos.len() == 0 {
print("No repositories found, skipping test");
return true;
}
let repo = repos[0];
let has_changes = git_has_changes(repo);
print(`Repository ${repo} has changes: ${has_changes}`);
return true;
}
// Test find_matching_repos function
fn test_find_matching_repos() {
print("Testing find_matching_repos...");
// Get all repositories with wildcard
let all_repos = git_list();
if all_repos.len() == 0 {
print("No repositories found, skipping test");
return true;
}
// Extract a part of the first repo name to search for
let repo_name = all_repos[0].split("/").last();
let search_pattern = repo_name.substring(0, 3) + "*";
print(`Searching for repositories matching pattern: ${search_pattern}`);
let matching = find_matching_repos(search_pattern);
print(`Found ${matching.len()} matching repositories`);
for repo in matching {
print(` - ${repo}`);
}
return matching.len() > 0;
}
// Run the tests
fn run_tests() {
let tests = [
#{ name: "git_clone", fn: test_git_clone },
#{ name: "git_list", fn: test_git_list },
#{ name: "git_has_changes", fn: test_git_has_changes },
#{ name: "find_matching_repos", fn: test_find_matching_repos }
];
let passed = 0;
let failed = 0;
for test in tests {
print(`\nRunning test: ${test.name}`);
let result = false;
try {
result = test.fn();
} catch(err) {
print(`Test ${test.name} threw an error: ${err}`);
result = false;
}
if result {
print(`Test ${test.name} PASSED`);
passed += 1;
} else {
print(`Test ${test.name} FAILED`);
failed += 1;
}
}
print(`\nTest summary: ${passed} passed, ${failed} failed`);
}
// Run all tests
run_tests();