Files
keep/src/meta_plugin/exec.rs
Andrew Phillips fed3722ef9 fix: Resolve compilation errors by refactoring imports and type annotations
Co-authored-by: aider (openai/andrew/openrouter/deepseek/deepseek-chat-v3.1) <aider@aider.chat>
2025-09-03 09:39:22 -03:00

271 lines
8.9 KiB
Rust

use log::*;
use std::io::Write;
use std::process::{Command, Stdio, Child};
use which::which;
use crate::meta_plugin::{MetaPlugin, MetaPluginResponse, MetaPluginType};
pub struct MetaPluginExec {
pub program: String,
pub args: Vec<String>,
pub supported: bool,
pub split_whitespace: bool,
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>,
}
// Manual Debug implementation because Box<dyn Write> doesn't implement Debug
impl std::fmt::Debug for MetaPluginExec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MetaPluginExec")
.field("program", &self.program)
.field("args", &self.args)
.field("supported", &self.supported)
.field("split_whitespace", &self.split_whitespace)
.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)
.finish()
}
}
impl MetaPluginExec {
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 {
fn is_supported(&self) -> bool {
self.supported
}
fn is_internal(&self) -> bool {
false
}
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,
}
}
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,
}
}
fn update(&mut self, data: &[u8]) -> MetaPluginResponse {
if let Some(ref mut writer) = self.writer {
if let Err(e) = writer.write_all(data) {
debug!("META: Failed to write to process stdin: {}", e);
}
}
MetaPluginResponse {
metadata: Vec::new(),
is_finalized: false,
}
}
fn meta_type(&self) -> MetaPluginType {
MetaPluginType::Exec
}
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
}
}
fn outputs(&self) -> &std::collections::HashMap<String, serde_yaml::Value> {
&self.outputs
}
fn outputs_mut(&mut self) -> &mut std::collections::HashMap<String, serde_yaml::Value> {
&mut self.outputs
}
fn default_outputs(&self) -> Vec<String> {
vec![self.meta_type().to_string()]
}
fn options(&self) -> &std::collections::HashMap<String, serde_yaml::Value> {
&self.options
}
fn options_mut(&mut self) -> &mut std::collections::HashMap<String, serde_yaml::Value> {
&mut self.options
}
}
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") {
if 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") {
if let Some(split_bool) = split_value.as_bool() {
split_whitespace = split_bool;
}
}
if let Some(name_value) = opts.get("name") {
if 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))
});
}