refactor: compose BaseMetaPlugin in remaining meta plugins
Co-authored-by: aider (openai/andrew/openrouter/sonoma-sky-alpha) <aider@aider.chat>
This commit is contained in:
@@ -3,7 +3,7 @@ use std::io::Write;
|
||||
use std::process::{Command, Stdio, Child};
|
||||
use which::which;
|
||||
|
||||
use crate::meta_plugin::{MetaPlugin, MetaPluginResponse, MetaPluginType};
|
||||
use crate::meta_plugin::{MetaPlugin, MetaPluginResponse, MetaPluginType, BaseMetaPlugin};
|
||||
|
||||
/// External program execution meta plugin.
|
||||
///
|
||||
@@ -22,8 +22,7 @@ pub struct MetaPluginExec {
|
||||
process: Option<Child>,
|
||||
writer: Option<Box<dyn Write>>,
|
||||
result: Option<String>,
|
||||
outputs: std::collections::HashMap<String, serde_yaml::Value>,
|
||||
options: std::collections::HashMap<String, serde_yaml::Value>,
|
||||
base: BaseMetaPlugin,
|
||||
}
|
||||
|
||||
// Manual Debug implementation because Box<dyn Write> doesn't implement Debug
|
||||
@@ -40,8 +39,7 @@ impl std::fmt::Debug for MetaPluginExec {
|
||||
.field("process", &self.process)
|
||||
.field("writer", &self.writer.as_ref().map(|_| "Box<dyn Write>"))
|
||||
.field("result", &self.result)
|
||||
.field("outputs", &self.outputs)
|
||||
.field("options", &self.options)
|
||||
.field("base", &self.base)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -69,337 +67,4 @@ impl MetaPluginExec {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// let plugin = MetaPluginExec::new("date", vec![], "timestamp", false, None, None);
|
||||
/// assert!(plugin.supported); // If 'date' is available
|
||||
/// ```
|
||||
pub fn new(
|
||||
program: &str,
|
||||
args: Vec<&str>,
|
||||
meta_name: String,
|
||||
split_whitespace: bool,
|
||||
_options: Option<std::collections::HashMap<String, serde_yaml::Value>>,
|
||||
outputs: Option<std::collections::HashMap<String, serde_yaml::Value>>,
|
||||
) -> MetaPluginExec {
|
||||
let program_path = which(program);
|
||||
let supported = program_path.is_ok();
|
||||
|
||||
// Start with default outputs
|
||||
let mut final_outputs = std::collections::HashMap::new();
|
||||
final_outputs.insert(meta_name.clone(), serde_yaml::Value::String(meta_name.clone()));
|
||||
if let Some(outs) = outputs {
|
||||
for (key, value) in outs {
|
||||
final_outputs.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Start with default options
|
||||
let mut final_options = std::collections::HashMap::new();
|
||||
if let Some(opts) = _options {
|
||||
for (key, value) in opts {
|
||||
final_options.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
MetaPluginExec {
|
||||
program: program_path.map_or_else(|_| program.to_string(), |p| p.to_string_lossy().to_string()),
|
||||
args: args.iter().map(|s| s.to_string()).collect(),
|
||||
supported,
|
||||
split_whitespace,
|
||||
process: None,
|
||||
writer: None,
|
||||
result: None,
|
||||
outputs: final_outputs,
|
||||
options: final_options,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl MetaPlugin for MetaPluginExec {
|
||||
/// Checks if the external program is available on the system.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `bool` - True if the program was found via `which`.
|
||||
fn is_supported(&self) -> bool {
|
||||
self.supported
|
||||
}
|
||||
|
||||
/// Indicates if this is an internal (built-in) plugin.
|
||||
///
|
||||
/// External exec plugins are always non-internal.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `false` - Always false for this plugin type.
|
||||
fn is_internal(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Initializes the plugin by spawning the external process.
|
||||
///
|
||||
/// Sets up piped stdin/stdout/stderr for the command. Does not finalize yet.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `MetaPluginResponse` - Empty metadata, not finalized.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If spawn fails, returns finalized response with no metadata (logs error).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// let mut plugin = MetaPluginExec::new("echo", vec!["hello"], "output", false, None, None);
|
||||
/// let response = plugin.initialize();
|
||||
/// assert!(!response.is_finalized);
|
||||
/// ```
|
||||
fn initialize(&mut self) -> MetaPluginResponse {
|
||||
debug!("META: Initializing program plugin: {:?}", self);
|
||||
|
||||
let program = self.program.clone();
|
||||
let args = self.args.clone();
|
||||
|
||||
debug!("META: Executing command: {:?} {:?}", program, args);
|
||||
|
||||
let mut process = match Command::new(program.clone())
|
||||
.args(args.clone())
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
{
|
||||
Ok(process) => process,
|
||||
Err(e) => {
|
||||
debug!("META: Failed to spawn process: {}", e);
|
||||
return MetaPluginResponse {
|
||||
metadata: Vec::new(),
|
||||
is_finalized: true,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
let stdin = process.stdin.take().unwrap();
|
||||
self.writer = Some(Box::new(stdin));
|
||||
self.process = Some(process);
|
||||
|
||||
MetaPluginResponse {
|
||||
metadata: Vec::new(),
|
||||
is_finalized: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Finalizes the plugin by waiting for the process to complete and capturing output.
|
||||
///
|
||||
/// Processes stdout based on split_whitespace option, adds to metadata if successful.
|
||||
/// Logs stderr and status on failure but does not propagate as error.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `MetaPluginResponse` - Metadata from output (if any), always finalized.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// let response = plugin.finalize();
|
||||
/// assert!(response.is_finalized);
|
||||
/// if let Some(meta) = response.metadata.first() {
|
||||
/// // Contains captured output
|
||||
/// }
|
||||
/// ```
|
||||
fn finalize(&mut self) -> MetaPluginResponse {
|
||||
debug!("META: Finalizing program plugin");
|
||||
let mut metadata = Vec::new();
|
||||
|
||||
if let Some(process) = self.process.take() {
|
||||
// Close stdin to signal end of input
|
||||
drop(self.writer.take());
|
||||
|
||||
// Wait for the process to complete
|
||||
let output = match process.wait_with_output() {
|
||||
Ok(output) => output,
|
||||
Err(e) => {
|
||||
debug!("META: Failed to get process output: {}", e);
|
||||
return MetaPluginResponse {
|
||||
metadata: Vec::new(),
|
||||
is_finalized: true,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
if output.status.success() {
|
||||
// Process the output
|
||||
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||
let result = if self.split_whitespace {
|
||||
output_str.split_whitespace().next().unwrap_or("").to_string()
|
||||
} else {
|
||||
output_str.trim().to_string()
|
||||
};
|
||||
|
||||
if !result.is_empty() {
|
||||
debug!("META: Program output: {}", result);
|
||||
self.result = Some(result.clone());
|
||||
|
||||
// Use process_metadata_outputs to handle output mapping
|
||||
if let Some(meta_data) = crate::meta_plugin::process_metadata_outputs(
|
||||
&self.meta_type().to_string(),
|
||||
serde_yaml::Value::String(result),
|
||||
&self.outputs
|
||||
) {
|
||||
metadata.push(meta_data);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug!("META: Program failed with status: {:?}", output.status);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
if !stderr.is_empty() {
|
||||
debug!("META: Program stderr: {}", stderr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MetaPluginResponse {
|
||||
metadata,
|
||||
is_finalized: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the plugin by writing data to the process's stdin.
|
||||
///
|
||||
/// Pipes incoming data (e.g., item content) to the command for processing.
|
||||
/// Logs write errors but continues.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `data` - Byte slice to write to stdin.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `MetaPluginResponse` - Empty metadata, not finalized.
|
||||
fn update(&mut self, data: &[u8]) -> MetaPluginResponse {
|
||||
if let Some(ref mut writer) = self.writer
|
||||
&& let Err(e) = writer.write_all(data) {
|
||||
debug!("META: Failed to write to process stdin: {}", e);
|
||||
}
|
||||
MetaPluginResponse {
|
||||
metadata: Vec::new(),
|
||||
is_finalized: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the type of this meta plugin.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `MetaPluginType::Exec` - The exec plugin type.
|
||||
fn meta_type(&self) -> MetaPluginType {
|
||||
MetaPluginType::Exec
|
||||
}
|
||||
|
||||
/// Provides information about the program and its arguments.
|
||||
///
|
||||
/// Only returns data if the program is supported.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Option<(&str, Vec<&str>)>` - Tuple of program path and arg slices, or None.
|
||||
fn program_info(&self) -> Option<(&str, Vec<&str>)> {
|
||||
if self.supported {
|
||||
Some((&self.program, self.args.iter().map(|s| s.as_str()).collect()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an immutable reference to the plugin's outputs.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `&HashMap<String, serde_yaml::Value>` - The outputs map.
|
||||
fn outputs(&self) -> &std::collections::HashMap<String, serde_yaml::Value> {
|
||||
&self.outputs
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the plugin's outputs.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `&mut HashMap<String, serde_yaml::Value>` - Mutable outputs map.
|
||||
fn outputs_mut(&mut self) -> &mut std::collections::HashMap<String, serde_yaml::Value> {
|
||||
&mut self.outputs
|
||||
}
|
||||
|
||||
/// Returns the default output keys for this plugin.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Vec<String>` - Vector with the exec type string.
|
||||
fn default_outputs(&self) -> Vec<String> {
|
||||
vec![self.meta_type().to_string()]
|
||||
}
|
||||
|
||||
|
||||
/// Returns an immutable reference to the plugin's options.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `&HashMap<String, serde_yaml::Value>` - The options map.
|
||||
fn options(&self) -> &std::collections::HashMap<String, serde_yaml::Value> {
|
||||
&self.options
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the plugin's options.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `&mut HashMap<String, serde_yaml::Value>` - Mutable options map.
|
||||
fn options_mut(&mut self) -> &mut std::collections::HashMap<String, serde_yaml::Value> {
|
||||
&mut self.options
|
||||
}
|
||||
}
|
||||
/// Registers the exec meta plugin with the global registry.
|
||||
///
|
||||
/// This constructor function is called at module load time using ctor crate.
|
||||
/// It parses "command" option for program/args, "split_whitespace" for output processing,
|
||||
/// and "name" for metadata key. Falls back to defaults if missing.
|
||||
use crate::meta_plugin::register_meta_plugin;
|
||||
|
||||
// Register the plugin at module initialization time
|
||||
#[ctor::ctor]
|
||||
fn register_exec_plugin() {
|
||||
register_meta_plugin(MetaPluginType::Exec, |options, outputs| {
|
||||
// For exec type, we need to parse the command from options
|
||||
let mut program_name = String::new();
|
||||
let mut args = Vec::new();
|
||||
let mut meta_name = MetaPluginType::Exec.to_string();
|
||||
let mut split_whitespace = true;
|
||||
|
||||
if let Some(opts) = &options {
|
||||
if let Some(command_value) = opts.get("command")
|
||||
&& let Some(command_str) = command_value.as_str() {
|
||||
let parts: Vec<&str> = command_str.split_whitespace().collect();
|
||||
if !parts.is_empty() {
|
||||
program_name = parts[0].to_string();
|
||||
args = parts[1..].iter().map(|s| s.to_string()).collect();
|
||||
}
|
||||
}
|
||||
// Handle other options if needed
|
||||
if let Some(split_value) = opts.get("split_whitespace")
|
||||
&& let Some(split_bool) = split_value.as_bool() {
|
||||
split_whitespace = split_bool;
|
||||
}
|
||||
if let Some(name_value) = opts.get("name")
|
||||
&& let Some(name_str) = name_value.as_str() {
|
||||
meta_name = name_str.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
Box::new(MetaPluginExec::new(&program_name,
|
||||
args.iter().map(|s| s.as_str()).collect(),
|
||||
meta_name,
|
||||
split_whitespace,
|
||||
options,
|
||||
outputs))
|
||||
});
|
||||
}
|
||||
/// let plugin
|
||||
Reference in New Issue
Block a user