mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-08 22:21:45 +00:00
Add delegate_execution_env macro to reduce decorator boilerplate
ReadBeforeWriteEnvironment had 9 pass-through methods that just forwarded to self.inner. The new delegate_execution_env! macro generates these automatically, so only the 4 customized methods (read_file, write_file, delete_file, grep) need to be written explicitly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b80f9cb100
commit
e8694f08bb
2 changed files with 112 additions and 83 deletions
|
|
@ -4,6 +4,85 @@ use std::fmt::Write;
|
|||
use std::sync::Arc;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
/// Generates an `#[async_trait] impl ExecutionEnvironment` block for a decorator type
|
||||
/// that wraps an `Arc<dyn ExecutionEnvironment>`. The caller provides custom method
|
||||
/// implementations; all remaining trait methods delegate to the inner field.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// ```ignore
|
||||
/// delegate_execution_env! {
|
||||
/// MyDecorator => inner {
|
||||
/// // Only provide methods with custom logic — the rest delegate automatically.
|
||||
/// async fn read_file(&self, path: &str, offset: Option<usize>, limit: Option<usize>) -> Result<String, String> {
|
||||
/// // custom logic...
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! delegate_execution_env {
|
||||
(
|
||||
$type:ty => $field:ident {
|
||||
$($custom:item)*
|
||||
}
|
||||
) => {
|
||||
#[async_trait::async_trait]
|
||||
impl $crate::execution_env::ExecutionEnvironment for $type {
|
||||
$($custom)*
|
||||
|
||||
async fn file_exists(&self, path: &str) -> Result<bool, String> {
|
||||
self.$field.file_exists(path).await
|
||||
}
|
||||
|
||||
async fn list_directory(
|
||||
&self,
|
||||
path: &str,
|
||||
depth: Option<usize>,
|
||||
) -> Result<Vec<$crate::execution_env::DirEntry>, String> {
|
||||
self.$field.list_directory(path, depth).await
|
||||
}
|
||||
|
||||
async fn exec_command(
|
||||
&self,
|
||||
command: &str,
|
||||
timeout_ms: u64,
|
||||
working_dir: Option<&str>,
|
||||
env_vars: Option<&std::collections::HashMap<String, String>>,
|
||||
cancel_token: Option<tokio_util::sync::CancellationToken>,
|
||||
) -> Result<$crate::execution_env::ExecResult, String> {
|
||||
self.$field
|
||||
.exec_command(command, timeout_ms, working_dir, env_vars, cancel_token)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn glob(&self, pattern: &str, path: Option<&str>) -> Result<Vec<String>, String> {
|
||||
self.$field.glob(pattern, path).await
|
||||
}
|
||||
|
||||
async fn initialize(&self) -> Result<(), String> {
|
||||
self.$field.initialize().await
|
||||
}
|
||||
|
||||
async fn cleanup(&self) -> Result<(), String> {
|
||||
self.$field.cleanup().await
|
||||
}
|
||||
|
||||
fn working_directory(&self) -> &str {
|
||||
self.$field.working_directory()
|
||||
}
|
||||
|
||||
fn platform(&self) -> &str {
|
||||
self.$field.platform()
|
||||
}
|
||||
|
||||
fn os_version(&self) -> String {
|
||||
self.$field.os_version()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Events emitted during execution environment lifecycle operations.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum ExecutionEnvEvent {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
use crate::execution_env::*;
|
||||
use async_trait::async_trait;
|
||||
use std::collections::HashSet;
|
||||
use std::path::{Component, PathBuf};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
/// Decorator that prevents writing to files the agent hasn't read first.
|
||||
///
|
||||
|
|
@ -73,93 +71,45 @@ impl ReadBeforeWriteEnvironment {
|
|||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ExecutionEnvironment for ReadBeforeWriteEnvironment {
|
||||
async fn read_file(
|
||||
&self,
|
||||
path: &str,
|
||||
offset: Option<usize>,
|
||||
limit: Option<usize>,
|
||||
) -> Result<String, String> {
|
||||
let result = self.inner.read_file(path, offset, limit).await?;
|
||||
self.mark_read(path);
|
||||
Ok(result)
|
||||
}
|
||||
crate::delegate_execution_env! {
|
||||
ReadBeforeWriteEnvironment => inner {
|
||||
async fn read_file(
|
||||
&self,
|
||||
path: &str,
|
||||
offset: Option<usize>,
|
||||
limit: Option<usize>,
|
||||
) -> Result<String, String> {
|
||||
let result = self.inner.read_file(path, offset, limit).await?;
|
||||
self.mark_read(path);
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
async fn write_file(&self, path: &str, content: &str) -> Result<(), String> {
|
||||
self.guard_write(path).await?;
|
||||
self.inner.write_file(path, content).await
|
||||
}
|
||||
async fn write_file(&self, path: &str, content: &str) -> Result<(), String> {
|
||||
self.guard_write(path).await?;
|
||||
self.inner.write_file(path, content).await
|
||||
}
|
||||
|
||||
async fn delete_file(&self, path: &str) -> Result<(), String> {
|
||||
self.guard_write(path).await?;
|
||||
self.inner.delete_file(path).await
|
||||
}
|
||||
async fn delete_file(&self, path: &str) -> Result<(), String> {
|
||||
self.guard_write(path).await?;
|
||||
self.inner.delete_file(path).await
|
||||
}
|
||||
|
||||
async fn file_exists(&self, path: &str) -> Result<bool, String> {
|
||||
self.inner.file_exists(path).await
|
||||
}
|
||||
|
||||
async fn list_directory(
|
||||
&self,
|
||||
path: &str,
|
||||
depth: Option<usize>,
|
||||
) -> Result<Vec<DirEntry>, String> {
|
||||
self.inner.list_directory(path, depth).await
|
||||
}
|
||||
|
||||
async fn exec_command(
|
||||
&self,
|
||||
command: &str,
|
||||
timeout_ms: u64,
|
||||
working_dir: Option<&str>,
|
||||
env_vars: Option<&std::collections::HashMap<String, String>>,
|
||||
cancel_token: Option<CancellationToken>,
|
||||
) -> Result<ExecResult, String> {
|
||||
self.inner
|
||||
.exec_command(command, timeout_ms, working_dir, env_vars, cancel_token)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn grep(
|
||||
&self,
|
||||
pattern: &str,
|
||||
path: &str,
|
||||
options: &GrepOptions,
|
||||
) -> Result<Vec<String>, String> {
|
||||
let results = self.inner.grep(pattern, path, options).await?;
|
||||
for line in &results {
|
||||
if let Some(file_path) = line.split(':').next() {
|
||||
if !file_path.is_empty() {
|
||||
self.mark_read(file_path);
|
||||
async fn grep(
|
||||
&self,
|
||||
pattern: &str,
|
||||
path: &str,
|
||||
options: &GrepOptions,
|
||||
) -> Result<Vec<String>, String> {
|
||||
let results = self.inner.grep(pattern, path, options).await?;
|
||||
for line in &results {
|
||||
if let Some(file_path) = line.split(':').next() {
|
||||
if !file_path.is_empty() {
|
||||
self.mark_read(file_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(results)
|
||||
}
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
async fn glob(&self, pattern: &str, path: Option<&str>) -> Result<Vec<String>, String> {
|
||||
self.inner.glob(pattern, path).await
|
||||
}
|
||||
|
||||
async fn initialize(&self) -> Result<(), String> {
|
||||
self.inner.initialize().await
|
||||
}
|
||||
|
||||
async fn cleanup(&self) -> Result<(), String> {
|
||||
self.inner.cleanup().await
|
||||
}
|
||||
|
||||
fn working_directory(&self) -> &str {
|
||||
self.inner.working_directory()
|
||||
}
|
||||
|
||||
fn platform(&self) -> &str {
|
||||
self.inner.platform()
|
||||
}
|
||||
|
||||
fn os_version(&self) -> String {
|
||||
self.inner.os_version()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue