use lazy_static::lazy_static;
use rhai::{Array, Dynamic, Engine, EvalAltResult, Map, Position, Scope};
use std::sync::Mutex;
use super::error::to_rhai_result;
use super::types::*;
use crate::Repository;
use crate::fs::{Find, FsInterface, Modify};
use std::sync::Arc;
fn fs_handle_write(fs: &mut RhaiFsHandle, path: String, content: String) {
fs.pending_ops.push(FsOp::Write {
path,
content: content.into_bytes(),
});
}
fn fs_handle_delete(fs: &mut RhaiFsHandle, path: String) {
fs.pending_ops.push(FsOp::Delete { path });
}
fn fs_handle_pending_count(fs: &mut RhaiFsHandle) -> i64 {
fs.pending_ops.len() as i64
}
fn fs_handle_has_pending(fs: &mut RhaiFsHandle) -> bool {
!fs.pending_ops.is_empty()
}
fn fs_handle_clear(fs: &mut RhaiFsHandle) {
fs.pending_ops.clear();
fs.message = None;
}
fn fs_handle_set_message(fs: &mut RhaiFsHandle, msg: String) {
fs.message = Some(msg);
}
fn fs_handle_commit(fs: &mut RhaiFsHandle) -> Result<String, Box<EvalAltResult>> {
if fs.pending_ops.is_empty() {
return Err(Box::new(EvalAltResult::ErrorRuntime(
"No pending operations to commit".into(),
Position::NONE,
)));
}
let repo = to_rhai_result(Repository::open_rw(&fs.repo_path))?;
let fs_iface = to_rhai_result(FsInterface::new(Arc::new(repo), &fs.author))?;
for op in &fs.pending_ops {
match op {
FsOp::Write { path, content } => {
to_rhai_result(fs_iface.write_file(path, content))?;
}
FsOp::Delete { path } => {
to_rhai_result(fs_iface.delete_file(path))?;
}
}
}
let hash = to_rhai_result(fs_iface.commit())?;
fs.pending_ops.clear();
fs.message = None;
Ok(hash)
}
fn fs_handle_read(fs: &mut RhaiFsHandle, path: String) -> Result<String, Box<EvalAltResult>> {
let repo = to_rhai_result(Repository::open_rw(&fs.repo_path))?;
let fs_iface = to_rhai_result(FsInterface::new(Arc::new(repo), &fs.author))?;
to_rhai_result(
fs_iface
.read_file(&path)
.map(|bytes| String::from_utf8_lossy(&bytes).to_string()),
)
}
fn fs_handle_exists(fs: &mut RhaiFsHandle, path: String) -> Result<bool, Box<EvalAltResult>> {
let repo = to_rhai_result(Repository::open_rw(&fs.repo_path))?;
let fs_iface = to_rhai_result(FsInterface::new(Arc::new(repo), &fs.author))?;
to_rhai_result(fs_iface.exists(&path))
}
fn fs_handle_list(fs: &mut RhaiFsHandle, dir: String) -> Result<Array, Box<EvalAltResult>> {
let repo = to_rhai_result(Repository::open_rw(&fs.repo_path))?;
let fs_iface = to_rhai_result(FsInterface::new(Arc::new(repo), &fs.author))?;
to_rhai_result(fs_iface.list_dir(&dir).map(|entries| {
entries
.into_iter()
.map(|e| Dynamic::from(e.name))
.collect::<Array>()
}))
}
type OutputSender = Box<dyn Fn(&str) + Send + Sync>;
lazy_static! {
static ref OUTPUT_SENDER: Mutex<Option<OutputSender>> = Mutex::new(None);
static ref OUTPUT_BUFFER: Mutex<String> = Mutex::new(String::new());
}
pub fn set_output_sender<F>(sender: F)
where
F: Fn(&str) + Send + Sync + 'static,
{
if let Ok(mut guard) = OUTPUT_SENDER.lock() {
*guard = Some(Box::new(sender));
}
}
pub fn clear_output_sender() {
if let Ok(mut guard) = OUTPUT_SENDER.lock() {
*guard = None;
}
}
pub fn take_output_buffer() -> String {
if let Ok(mut buf) = OUTPUT_BUFFER.lock() {
std::mem::take(&mut *buf)
} else {
String::new()
}
}
fn append_output(s: &str) {
if let Ok(guard) = OUTPUT_SENDER.lock() {
if let Some(ref sender) = *guard {
sender(s);
return;
}
}
if let Ok(mut buf) = OUTPUT_BUFFER.lock() {
buf.push_str(s);
buf.push('\n');
}
}
pub struct ForgeEngine {
engine: Engine,
}
impl ForgeEngine {
pub fn new() -> Result<Self, Box<EvalAltResult>> {
let mut engine = Engine::new();
configure_engine(&mut engine);
register_types(&mut engine);
register_repo_functions(&mut engine);
register_fs_functions(&mut engine);
register_file_functions(&mut engine);
register_branch_tag_functions(&mut engine);
register_modify_functions(&mut engine);
register_find_functions(&mut engine);
register_utility_functions(&mut engine);
Ok(Self { engine })
}
pub fn engine(&self) -> &Engine {
&self.engine
}
pub fn engine_mut(&mut self) -> &mut Engine {
&mut self.engine
}
pub fn eval<T: Clone + Send + Sync + 'static>(
&self,
script: &str,
) -> Result<T, Box<EvalAltResult>> {
self.engine.eval(script)
}
pub fn run(&self, script: &str) -> Result<(), Box<EvalAltResult>> {
self.engine.run(script)
}
pub fn eval_with_scope<T: Clone + Send + Sync + 'static>(
&self,
scope: &mut Scope,
script: &str,
) -> Result<T, Box<EvalAltResult>> {
self.engine.eval_with_scope(scope, script)
}
pub fn run_with_scope(
&self,
scope: &mut Scope,
script: &str,
) -> Result<(), Box<EvalAltResult>> {
self.engine.run_with_scope(scope, script)
}
pub fn compile(&self, script: &str) -> Result<rhai::AST, Box<EvalAltResult>> {
self.engine
.compile(script)
.map_err(|e| Box::new(EvalAltResult::from(e)))
}
}
impl Default for ForgeEngine {
fn default() -> Self {
Self::new().expect("Failed to create ForgeEngine")
}
}
fn configure_engine(engine: &mut Engine) {
engine.set_max_expr_depths(64, 64);
engine.set_max_call_levels(64);
engine.set_max_operations(0); engine.set_max_modules(100);
engine.set_max_string_size(10_000_000); engine.set_max_array_size(100_000);
engine.set_max_map_size(100_000);
engine.on_print(|s| {
append_output(s);
});
engine.on_debug(|s, source, pos| {
let location = if let Some(src) = source {
format!("[{}:{}] ", src, pos)
} else if !pos.is_none() {
format!("[{}] ", pos)
} else {
String::new()
};
append_output(&format!("[DEBUG] {}{}", location, s));
});
}
fn register_types(engine: &mut Engine) {
engine.build_type::<RhaiRepo>();
engine.build_type::<RhaiFsHandle>();
engine.build_type::<RhaiFileInfo>();
engine.build_type::<RhaiCheckIn>();
engine.build_type::<RhaiModify>();
engine.build_type::<RhaiFind>();
}
fn register_repo_functions(engine: &mut Engine) {
engine.register_fn(
"repo_open",
|path: &str| -> Result<RhaiRepo, Box<EvalAltResult>> {
to_rhai_result(Repository::open(path).map(|_| RhaiRepo::new(path.to_string(), false)))
},
);
engine.register_fn(
"repo_open_rw",
|path: &str| -> Result<RhaiRepo, Box<EvalAltResult>> {
to_rhai_result(Repository::open_rw(path).map(|_| RhaiRepo::new(path.to_string(), true)))
},
);
engine.register_fn(
"repo_init",
|path: &str| -> Result<RhaiRepo, Box<EvalAltResult>> {
match Repository::init(path) {
Ok(_) => Ok(RhaiRepo::new(path.to_string(), true)),
Err(_) => {
to_rhai_result(
Repository::open_rw(path).map(|_| RhaiRepo::new(path.to_string(), true)),
)
}
}
},
);
engine.register_fn(
"repo_rebuild",
|repo: &mut RhaiRepo| -> Result<(), Box<EvalAltResult>> {
let r = if repo.read_write {
Repository::open_rw(&repo.path)
} else {
Repository::open(&repo.path)
};
to_rhai_result(r.and_then(|r| r.rebuild()))
},
);
engine.register_fn(
"repo_project_name",
|repo: &mut RhaiRepo| -> Result<String, Box<EvalAltResult>> {
let r = Repository::open(&repo.path);
to_rhai_result(
r.and_then(|r| r.project_name())
.map(|n| n.unwrap_or_default()),
)
},
);
engine.register_fn(
"repo_project_code",
|repo: &mut RhaiRepo| -> Result<String, Box<EvalAltResult>> {
let r = Repository::open(&repo.path);
to_rhai_result(r.and_then(|r| r.project_code()))
},
);
}
fn register_fs_functions(engine: &mut Engine) {
engine.register_fn("fs_new", |repo: RhaiRepo, author: &str| -> RhaiFsHandle {
RhaiFsHandle::new(repo.path, author.to_string())
});
engine.register_fn("fs_get", |repo: RhaiRepo, author: &str| -> RhaiFsHandle {
RhaiFsHandle::new(repo.path, author.to_string())
});
engine.register_fn(
"fs_read",
|fs: &mut RhaiFsHandle, path: &str| -> Result<String, Box<EvalAltResult>> {
let repo = to_rhai_result(Repository::open_rw(&fs.repo_path))?;
let fs_iface = to_rhai_result(FsInterface::new(Arc::new(repo), &fs.author))?;
to_rhai_result(
fs_iface
.read_file(path)
.map(|bytes| String::from_utf8_lossy(&bytes).to_string()),
)
},
);
engine.register_fn(
"fs_read_bytes",
|fs: &mut RhaiFsHandle, path: &str| -> Result<rhai::Blob, Box<EvalAltResult>> {
let repo = to_rhai_result(Repository::open_rw(&fs.repo_path))?;
let fs_iface = to_rhai_result(FsInterface::new(Arc::new(repo), &fs.author))?;
to_rhai_result(fs_iface.read_file(path))
},
);
engine.register_fn(
"fs_write",
|fs: &mut RhaiFsHandle, path: &str, content: &str| -> Result<(), Box<EvalAltResult>> {
let repo = to_rhai_result(Repository::open_rw(&fs.repo_path))?;
let fs_iface = to_rhai_result(FsInterface::new(Arc::new(repo), &fs.author))?;
to_rhai_result(fs_iface.write_file(path, content.as_bytes()))
},
);
engine.register_fn(
"fs_write_bytes",
|fs: &mut RhaiFsHandle,
path: &str,
content: rhai::Blob|
-> Result<(), Box<EvalAltResult>> {
let repo = to_rhai_result(Repository::open_rw(&fs.repo_path))?;
let fs_iface = to_rhai_result(FsInterface::new(Arc::new(repo), &fs.author))?;
to_rhai_result(fs_iface.write_file(path, &content))
},
);
engine.register_fn(
"fs_exists",
|fs: &mut RhaiFsHandle, path: &str| -> Result<bool, Box<EvalAltResult>> {
let repo = to_rhai_result(Repository::open_rw(&fs.repo_path))?;
let fs_iface = to_rhai_result(FsInterface::new(Arc::new(repo), &fs.author))?;
to_rhai_result(fs_iface.exists(path))
},
);
engine.register_fn(
"fs_delete",
|fs: &mut RhaiFsHandle, path: &str| -> Result<(), Box<EvalAltResult>> {
let repo = to_rhai_result(Repository::open_rw(&fs.repo_path))?;
let fs_iface = to_rhai_result(FsInterface::new(Arc::new(repo), &fs.author))?;
to_rhai_result(fs_iface.delete_file(path))
},
);
engine.register_fn(
"fs_list",
|fs: &mut RhaiFsHandle, dir: &str| -> Result<Array, Box<EvalAltResult>> {
let repo = to_rhai_result(Repository::open_rw(&fs.repo_path))?;
let fs_iface = to_rhai_result(FsInterface::new(Arc::new(repo), &fs.author))?;
to_rhai_result(fs_iface.list_dir(dir).map(|entries| {
entries
.into_iter()
.map(|e| Dynamic::from(e.name))
.collect::<Array>()
}))
},
);
engine.register_fn(
"fs_commit",
|fs: &mut RhaiFsHandle| -> Result<String, Box<EvalAltResult>> {
let repo = to_rhai_result(Repository::open_rw(&fs.repo_path))?;
let fs_iface = to_rhai_result(FsInterface::new(Arc::new(repo), &fs.author))?;
to_rhai_result(fs_iface.commit())
},
);
engine.register_fn(
"fs_status",
|fs: &mut RhaiFsHandle| -> Result<Map, Box<EvalAltResult>> {
let repo = to_rhai_result(Repository::open_rw(&fs.repo_path))?;
let fs_iface = to_rhai_result(FsInterface::new(Arc::new(repo), &fs.author))?;
let status = fs_iface.status();
let mut map = Map::new();
let status_str = match status {
crate::fs::FsInterfaceStatus::Active => "active",
crate::fs::FsInterfaceStatus::Committing => "committing",
crate::fs::FsInterfaceStatus::Closed => "closed",
};
map.insert("status".into(), Dynamic::from(status_str));
map.insert("has_changes".into(), Dynamic::from(fs_iface.has_changes()));
Ok(map)
},
);
engine.register_fn("write", fs_handle_write);
engine.register_fn("delete", fs_handle_delete);
engine.register_fn("commit", fs_handle_commit);
engine.register_fn("read", fs_handle_read);
engine.register_fn("exists", fs_handle_exists);
engine.register_fn("list", fs_handle_list);
engine.register_fn("pending_count", fs_handle_pending_count);
engine.register_fn("has_pending", fs_handle_has_pending);
engine.register_fn("clear", fs_handle_clear);
engine.register_fn("message", fs_handle_set_message);
}
fn register_file_functions(engine: &mut Engine) {
engine.register_fn(
"files_list",
|repo: &mut RhaiRepo| -> Result<Array, Box<EvalAltResult>> {
let r = to_rhai_result(Repository::open(&repo.path))?;
to_rhai_result(r.files().on_trunk().list().map(|files| {
files
.into_iter()
.map(|f| {
Dynamic::from(RhaiFileInfo {
name: f.name,
hash: f.hash,
size: f.size.unwrap_or(0) as i64,
})
})
.collect::<Array>()
}))
},
);
engine.register_fn(
"files_list_branch",
|repo: &mut RhaiRepo, branch: &str| -> Result<Array, Box<EvalAltResult>> {
let r = to_rhai_result(Repository::open(&repo.path))?;
to_rhai_result(r.files().on_branch(branch).list().map(|files| {
files
.into_iter()
.map(|f| {
Dynamic::from(RhaiFileInfo {
name: f.name,
hash: f.hash,
size: f.size.unwrap_or(0) as i64,
})
})
.collect::<Array>()
}))
},
);
engine.register_fn(
"files_read",
|repo: &mut RhaiRepo, path: &str| -> Result<String, Box<EvalAltResult>> {
let r = to_rhai_result(Repository::open(&repo.path))?;
to_rhai_result(r.files().on_trunk().read_string(path))
},
);
engine.register_fn(
"files_read_branch",
|repo: &mut RhaiRepo, branch: &str, path: &str| -> Result<String, Box<EvalAltResult>> {
let r = to_rhai_result(Repository::open(&repo.path))?;
to_rhai_result(r.files().on_branch(branch).read_string(path))
},
);
engine.register_fn(
"files_read_bytes",
|repo: &mut RhaiRepo, path: &str| -> Result<rhai::Blob, Box<EvalAltResult>> {
let r = to_rhai_result(Repository::open(&repo.path))?;
to_rhai_result(r.files().on_trunk().read(path))
},
);
engine.register_fn(
"files_find",
|repo: &mut RhaiRepo, pattern: &str| -> Result<Array, Box<EvalAltResult>> {
let r = to_rhai_result(Repository::open(&repo.path))?;
to_rhai_result(r.files().on_trunk().find(pattern).map(|files| {
files
.into_iter()
.map(|f| {
Dynamic::from(RhaiFileInfo {
name: f.name,
hash: f.hash,
size: f.size.unwrap_or(0) as i64,
})
})
.collect::<Array>()
}))
},
);
engine.register_fn(
"files_find_branch",
|repo: &mut RhaiRepo, branch: &str, pattern: &str| -> Result<Array, Box<EvalAltResult>> {
let r = to_rhai_result(Repository::open(&repo.path))?;
to_rhai_result(r.files().on_branch(branch).find(pattern).map(|files| {
files
.into_iter()
.map(|f| {
Dynamic::from(RhaiFileInfo {
name: f.name,
hash: f.hash,
size: f.size.unwrap_or(0) as i64,
})
})
.collect::<Array>()
}))
},
);
}
fn register_branch_tag_functions(engine: &mut Engine) {
engine.register_fn(
"branches_list",
|repo: &mut RhaiRepo| -> Result<Array, Box<EvalAltResult>> {
let r = to_rhai_result(Repository::open(&repo.path))?;
to_rhai_result(
r.branches()
.list()
.map(|branches| branches.into_iter().map(Dynamic::from).collect::<Array>()),
)
},
);
engine.register_fn(
"branches_create",
|repo: &mut RhaiRepo,
name: &str,
from_branch: &str,
author: &str|
-> Result<String, Box<EvalAltResult>> {
let r = to_rhai_result(Repository::open_rw(&repo.path))?;
to_rhai_result(
r.branches()
.create(name)
.from_branch(from_branch)
.author(author)
.execute(),
)
},
);
engine.register_fn(
"branches_tip",
|repo: &mut RhaiRepo, branch: &str| -> Result<String, Box<EvalAltResult>> {
let r = to_rhai_result(Repository::open(&repo.path))?;
to_rhai_result(r.history().branch_tip(branch).map(|ci| ci.hash))
},
);
engine.register_fn(
"tags_list",
|repo: &mut RhaiRepo| -> Result<Array, Box<EvalAltResult>> {
let r = to_rhai_result(Repository::open(&repo.path))?;
to_rhai_result(
r.tags()
.list()
.map(|tags| tags.into_iter().map(Dynamic::from).collect::<Array>()),
)
},
);
engine.register_fn(
"tags_create",
|repo: &mut RhaiRepo,
name: &str,
at_branch: &str,
author: &str|
-> Result<String, Box<EvalAltResult>> {
let r = to_rhai_result(Repository::open_rw(&repo.path))?;
to_rhai_result(
r.tags()
.create(name)
.at_branch(at_branch)
.author(author)
.execute(),
)
},
);
engine.register_fn(
"tags_create_at_commit",
|repo: &mut RhaiRepo,
name: &str,
commit: &str,
author: &str|
-> Result<String, Box<EvalAltResult>> {
let r = to_rhai_result(Repository::open_rw(&repo.path))?;
to_rhai_result(
r.tags()
.create(name)
.at_commit(commit)
.author(author)
.execute(),
)
},
);
engine.register_fn(
"history_recent",
|repo: &mut RhaiRepo, limit: i64| -> Result<Array, Box<EvalAltResult>> {
let r = to_rhai_result(Repository::open(&repo.path))?;
to_rhai_result(r.history().recent(limit as usize).map(|commits| {
commits
.into_iter()
.map(|c| {
Dynamic::from(RhaiCheckIn {
hash: c.hash,
timestamp: c.timestamp,
user: c.user,
comment: c.comment,
branch: c.branch.unwrap_or_default(),
})
})
.collect::<Array>()
}))
},
);
engine.register_fn(
"commit",
|repo: &mut RhaiRepo,
message: &str,
author: &str,
files: Map|
-> Result<String, Box<EvalAltResult>> {
let r = to_rhai_result(Repository::open_rw(&repo.path))?;
let mut builder = r.commit_builder().message(message).author(author);
for (path, content) in files {
let path_str = path.to_string();
if let Some(s) = content.clone().try_cast::<String>() {
builder = builder.file(&path_str, s.as_bytes());
} else if let Some(blob) = content.clone().try_cast::<rhai::Blob>() {
builder = builder.file(&path_str, &blob);
} else {
return Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Invalid content type for file: {}", path_str).into(),
Position::NONE,
)));
}
}
to_rhai_result(builder.execute())
},
);
engine.register_fn(
"commit_on_branch",
|repo: &mut RhaiRepo,
message: &str,
author: &str,
branch: &str,
files: Map|
-> Result<String, Box<EvalAltResult>> {
let r = to_rhai_result(Repository::open_rw(&repo.path))?;
let tip = to_rhai_result(r.history().branch_tip(branch))?;
let mut builder = r
.commit_builder()
.message(message)
.author(author)
.parent(&tip.hash);
for (path, content) in files {
let path_str = path.to_string();
if let Some(s) = content.clone().try_cast::<String>() {
builder = builder.file(&path_str, s.as_bytes());
} else if let Some(blob) = content.clone().try_cast::<rhai::Blob>() {
builder = builder.file(&path_str, &blob);
} else {
return Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Invalid content type for file: {}", path_str).into(),
Position::NONE,
)));
}
}
to_rhai_result(builder.execute())
},
);
}
fn register_modify_functions(engine: &mut Engine) {
engine.register_fn("modify_new", |repo: RhaiRepo| -> RhaiModify {
RhaiModify::new(repo.path)
});
engine.register_fn(
"modify_execute",
|modify: &mut RhaiModify| -> Result<String, Box<EvalAltResult>> {
let repo = to_rhai_result(Repository::open_rw(&modify.repo_path))?;
let message = modify
.message
.clone()
.unwrap_or_else(|| "Modify operations".to_string());
let author = modify.author.clone().unwrap_or_else(|| "rhai".to_string());
let mut builder = Modify::new(&repo).message(&message).author(&author);
if let Some(ref branch) = modify.branch {
builder = to_rhai_result(builder.on_branch(branch))?;
}
for op in &modify.operations {
builder = match op {
RhaiOp::CopyFile { src, dst } => builder.copy_file(src, dst),
RhaiOp::CopyDir { src, dst } => builder.copy_dir(src, dst),
RhaiOp::MoveFile { src, dst } => builder.move_file(src, dst),
RhaiOp::MoveDir { src, dst } => builder.move_dir(src, dst),
RhaiOp::DeleteFile { path } => builder.delete_file(path),
RhaiOp::DeleteDir { path } => builder.delete_dir(path),
RhaiOp::DeleteMatching { pattern } => builder.delete_matching(pattern),
RhaiOp::Chmod { path, mode } => builder.chmod(path, *mode as u32),
RhaiOp::ChmodDir { path, mode } => builder.chmod_dir(path, *mode as u32),
RhaiOp::MakeExecutable { path } => builder.make_executable(path),
RhaiOp::Symlink { link, target } => builder.symlink(link, target),
RhaiOp::WriteFile { path, content } => builder.write(path, content),
};
}
to_rhai_result(builder.execute())
},
);
}
fn register_find_functions(engine: &mut Engine) {
engine.register_fn("find_new", |repo: RhaiRepo| -> RhaiFind {
RhaiFind::new(repo.path)
});
engine.register_fn(
"find_execute",
|find: &mut RhaiFind| -> Result<Array, Box<EvalAltResult>> {
let repo = to_rhai_result(Repository::open(&find.repo_path))?;
let mut builder = Find::new(&repo);
if let Some(ref pattern) = find.pattern {
builder = builder.pattern(pattern);
}
if let Some(ref branch) = find.branch {
builder = to_rhai_result(builder.on_branch(branch))?;
}
for ignore in &find.ignores {
builder = builder.ignore(ignore);
}
if find.ignore_hidden {
builder = builder.ignore_hidden();
}
to_rhai_result(
builder
.paths()
.map(|paths| paths.into_iter().map(Dynamic::from).collect::<Array>()),
)
},
);
engine.register_fn(
"find",
|repo: &mut RhaiRepo, pattern: &str| -> Result<Array, Box<EvalAltResult>> {
let r = to_rhai_result(Repository::open(&repo.path))?;
to_rhai_result(
Find::new(&r)
.pattern(pattern)
.paths()
.map(|paths| paths.into_iter().map(Dynamic::from).collect::<Array>()),
)
},
);
engine.register_fn(
"exists",
|repo: &mut RhaiRepo, path: &str| -> Result<bool, Box<EvalAltResult>> {
let r = to_rhai_result(Repository::open(&repo.path))?;
to_rhai_result(crate::fs::exists(&r, path))
},
);
engine.register_fn(
"is_dir",
|repo: &mut RhaiRepo, path: &str| -> Result<bool, Box<EvalAltResult>> {
let r = to_rhai_result(Repository::open(&repo.path))?;
to_rhai_result(crate::fs::is_dir(&r, path))
},
);
engine.register_fn(
"du",
|repo: &mut RhaiRepo, pattern: &str| -> Result<i64, Box<EvalAltResult>> {
let r = to_rhai_result(Repository::open(&repo.path))?;
to_rhai_result(crate::fs::du(&r, pattern).map(|s| s as i64))
},
);
engine.register_fn(
"count",
|repo: &mut RhaiRepo, pattern: &str| -> Result<i64, Box<EvalAltResult>> {
let r = to_rhai_result(Repository::open(&repo.path))?;
to_rhai_result(crate::fs::count(&r, pattern).map(|c| c as i64))
},
);
}
fn register_utility_functions(engine: &mut Engine) {
engine.register_fn("sleep", |ms: i64| {
std::thread::sleep(std::time::Duration::from_millis(ms as u64));
});
engine.register_fn("env", |name: &str| -> String {
std::env::var(name).unwrap_or_default()
});
engine.register_fn("env_set", |name: &str, value: &str| {
unsafe {
std::env::set_var(name, value);
}
});
engine.register_fn("cwd", || -> String {
std::env::current_dir()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_default()
});
engine.register_fn("home", || -> String {
dirs::home_dir()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_default()
});
engine.register_fn("path_join", |parts: Array| -> String {
let path: std::path::PathBuf = parts
.into_iter()
.filter_map(|p| p.try_cast::<String>())
.collect();
path.to_string_lossy().to_string()
});
engine.register_fn("path_exists", |path: &str| -> bool {
std::path::Path::new(path).exists()
});
engine.register_fn("path_is_file", |path: &str| -> bool {
std::path::Path::new(path).is_file()
});
engine.register_fn("path_is_dir", |path: &str| -> bool {
std::path::Path::new(path).is_dir()
});
engine.register_fn("timestamp", || -> i64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0)
});
engine.register_fn("timestamp_ms", || -> i64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as i64)
.unwrap_or(0)
});
engine.register_fn("uuid", || -> String { uuid::Uuid::new_v4().to_string() });
engine.register_fn("hex_encode", |data: rhai::Blob| -> String {
hex::encode(&data)
});
engine.register_fn(
"hex_decode",
|s: &str| -> Result<rhai::Blob, Box<EvalAltResult>> {
hex::decode(s).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Hex decode error: {}", e).into(),
Position::NONE,
))
})
},
);
engine.register_fn("version", || -> String {
env!("CARGO_PKG_VERSION").to_string()
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_engine_creation() {
let engine = ForgeEngine::new().unwrap();
let result: i64 = engine.eval("1 + 2").unwrap();
assert_eq!(result, 3);
}
#[test]
fn test_utility_functions() {
let engine = ForgeEngine::new().unwrap();
let version: String = engine.eval("version()").unwrap();
assert!(!version.is_empty());
let uuid: String = engine.eval("uuid()").unwrap();
assert_eq!(uuid.len(), 36);
let ts: i64 = engine.eval("timestamp()").unwrap();
assert!(ts > 0);
}
}